RC and Relays

Hi

I am new, and I am trying to get my RC to control relays through my arduino. I have some code that enables me to control servos through the arduino, so far so good. I can see the bandwidth on the monitor, and it runs as you would expect from 1100 to 1900 with 1500 neutral on a given channel and controls a servo nicely.

I have a set of relays which require LOW to the pins to make the relay work. I want to make several relays work on each channel but now just staring with one.

if (unThrottleIn < 1200)
{
digitalWrite(THROTTLE_OUT_PIN, LOW);
}
else if (unThrottleIn > 1200)
{
digitalWrite(THROTTLE_OUT_PIN, HIGH);
}

When I use this code and move the transmitter arm to less than 1200 the relay comes on, but instead of being off it ‘judders’ on and off when the pin is on high.

Any help?

John

Are you driving the relay directly from the arduino pin, or do you have a transistor or other circuit in between the arduino and the relay? Assuming you are driving the relay directly, what current does your relay require to energize it? When you energize the relay, you may be attempting to pull too much current from the arduino pin. This may be causing a cascade of problems even including a reset of the arduino processor. Additionally, are you using a protection diode across the relay coil? If not, when you turn the relay off you may getting voltage spikes on arduino relay pin which could cause other problems. You have to be careful when driving relays directly from an arduino pin. The relay energize current must be very low, and I would suggest using protection diodes. Here's a link that talks about relays and the use of protection (flyback) diodes. http://www.physics.unlv.edu/~bill/PHYS483/relay.pdf I hope this helps. --dsmavis

Johnredearth: When I use this code and move the transmitter arm to less than 1200 the relay comes on, but instead of being off it 'judders' on and off when the pin is on high.

In addition to what @dsmavis says, you should have a dead band in your on-off range - for example OFF when the value is below 1100 and ON when it is above 1300. You can experiment to find how wide (or narrow) the deadband needs to be.

...R

Can you please post your whole program so that we can see how and where you are deriving the value of unThrottleIn

OK

Thank you for your responses. The relay bank is: 8 Channel 5V Relay Shield Module Board for Arduino PIC PLC AVR MCU DSP ARM. I have put up the basic blink sketch and it is working completely well with this. I have an external power source for the relays and it is all grounded with the arduino. I can get the relay to go on/off etc nicely.
I have tried to increase the deadband but it still ‘judders.’ Here is my ‘hacked’ code. It has far too much in it which is unnecessary but for now it is fine to get this right.

The following is the code. The ‘johns additions’ are what I am playing with

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

// Johns additions

Serial.println(unThrottleIn);
//Serial.println(unSteeringIn);
//Serial.println(unAuxIn);

if (unThrottleIn < 1200)
{
digitalWrite(THROTTLE_OUT_PIN, LOW);
}
else if (unThrottleIn > 1350)
{
digitalWrite(THROTTLE_OUT_PIN, HIGH);
}

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

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

Hi

Realised the problem. My outpin is also getting the servo output. Very basic. I will fix, and trim this code down part by part and get back to you when necessary!!

John

Hi all
Things now are coming along. Pls note the sketch below that has been adapted to my needs.
The whole sketch is about half way there currently and I can get all the relays to work as I wish, although those that work off the ‘ballast’ channel have a problem in that the ‘inner’ switches (pump) needless to say switch on when I travel the arm to the ‘pistons’ switches. I would like both the piston and the pump switches to be delayed for a second so that doesn’t happen.

Another reason for this is that I want to use the ballast controller, and ‘tap’ it twice (less than a second) to the left or right start a subroutine that I have not written yet, but will be called ‘diveroutine’ and the other side ‘surfaceroutine.’ This will involve specific times for the trim, piston and pump to come on and off. This is an issue for another day but if anyone can help with the delaying code, and the double trigger that would be great. I can then get to work and build the next section, in which I need to connect and read a water pressure sensor.

// 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 BALLAST_IN_PIN 2
#define KNOB_IN_PIN 3
#define AUX_IN_PIN 4
#define HYDRO_IN_PIN 5

// Assign your channel out pins
//#define PISTONOUT_OUT_PIN 5
//#define STEERING_OUT_PIN 6
//#define AUX_OUT_PIN 7
#define HYDRO_OUT_PIN 13

int PISTON_DIVE_PIN = 6;
int PISTON_SURFACE_PIN = 7;
int PUMP_DIVE_PIN = 8;
int PUMP_SURFACE_PIN = 9;
int TRIM_TO_BOW_PIN = 10;
int TRIM_TO_STERN_PIN = 11;
int WATER_GUN_PIN = 12;

// 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 servoBallast;
Servo servoKnob;
Servo servoAux;
Servo servoHydro;

// These bit flags are set in bUpdateFlagsShared to indicate which
// channels have new signals
#define BALLAST_FLAG 1
#define KNOB_FLAG 2
#define AUX_FLAG 4
#define HYDRO_FLAG 5

// 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 unBallastInShared;
volatile uint16_t unKnobInShared;
volatile uint16_t unAuxInShared;
volatile uint16_t unHydroInShared;

// 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 ulBallastStart;
uint32_t ulKnobStart;
uint32_t ulAuxStart;
uint32_t ulHydroStart;

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
//servoBallast.attach(PISTONOUT_OUT_PIN);
//servoSteering.attach(STEERING_OUT_PIN);
//servoAux.attach(AUX_OUT_PIN);
servoHydro.attach(HYDRO_OUT_PIN);

pinMode(PISTON_DIVE_PIN, OUTPUT);
pinMode(PISTON_SURFACE_PIN, OUTPUT);
pinMode(PUMP_DIVE_PIN, OUTPUT);
pinMode(PUMP_SURFACE_PIN, OUTPUT);
pinMode(TRIM_TO_BOW_PIN, OUTPUT);
pinMode(TRIM_TO_STERN_PIN, OUTPUT);
pinMode(WATER_GUN_PIN, OUTPUT);

(PISTON_DIVE_PIN, HIGH);
(PISTON_SURFACE_PIN, HIGH);
(PUMP_DIVE_PIN, HIGH);
(PUMP_SURFACE_PIN, HIGH);
(TRIM_TO_BOW_PIN, HIGH);
(TRIM_TO_STERN_PIN, HIGH);
(WATER_GUN_PIN, HIGH);

// using the PinChangeInt library, attach the interrupts
// used to read the channels
PCintPort::attachInterrupt(BALLAST_IN_PIN, calcBallast,CHANGE);
PCintPort::attachInterrupt(KNOB_IN_PIN, calcKnob,CHANGE);
PCintPort::attachInterrupt(AUX_IN_PIN, calcAux,CHANGE);
PCintPort::attachInterrupt(HYDRO_IN_PIN, calcHydro,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 unBallastIn;
static uint16_t unKnobIn;
static uint16_t unAuxIn;
static uint16_t unHydroIn;

// local copy of update flags
static uint8_t bUpdateFlags;

//Serial.println(unBallastIn);
//Serial.println(unKnobIn);
//Serial.println(unAuxIn);
Serial.println(unHydroIn);

//PISTON PARAMETERS

if (unBallastIn > 1800)
{
digitalWrite(PISTON_DIVE_PIN, LOW);
}
else if (unBallastIn < 1800)
{
digitalWrite(PISTON_DIVE_PIN, HIGH);
}
if (unBallastIn < 1300)
{
digitalWrite(PISTON_SURFACE_PIN, LOW);
}
else if (unBallastIn > 1300)
{
digitalWrite(PISTON_SURFACE_PIN, HIGH);
}

// BALLAST PUMP PARAMETERS

if (unBallastIn > 1600)
{
digitalWrite(PUMP_DIVE_PIN, LOW);
}
else if (unBallastIn < 1600)
{
digitalWrite(PUMP_DIVE_PIN, HIGH);
}
if (unBallastIn > 1700)
{
digitalWrite(PUMP_DIVE_PIN, HIGH);
}

// Trim tank parameters

if (unBallastIn < 1450)
{
digitalWrite(PUMP_SURFACE_PIN, LOW);
}
else if (unBallastIn > 1450)
{
digitalWrite(PUMP_SURFACE_PIN, HIGH);
}
if (unBallastIn < 1350)
{
digitalWrite(PUMP_SURFACE_PIN, HIGH);
}

if (unKnobIn > 1950)
{
digitalWrite(TRIM_TO_BOW_PIN, LOW);
}
else if (unKnobIn < 1950)
{
digitalWrite(TRIM_TO_BOW_PIN, HIGH);
}
if (unKnobIn < 1150)
{
digitalWrite(TRIM_TO_STERN_PIN, LOW);
}
else if (unKnobIn > 1150)
{
digitalWrite(TRIM_TO_STERN_PIN, HIGH);
}

// water pump switch gun

if (unKnobIn > 1700)
{
digitalWrite(WATER_GUN_PIN, LOW);
}
else if (unKnobIn < 1700)
{
digitalWrite(WATER_GUN_PIN, HIGH);
}
if (unKnobIn > 1800)
{
digitalWrite(WATER_GUN_PIN, HIGH);
}

// 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&BALLAST_FLAG)
{
unBallastIn = unBallastInShared;
}

if(bUpdateFlags& KNOB_FLAG)
{
unKnobIn = unKnobInShared;
}

if(bUpdateFlags& AUX_FLAG)
{
unAuxIn = unAuxInShared;
}

if(bUpdateFlags& HYDRO_FLAG)
{
unHydroIn = unHydroInShared;
}

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

// 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&BALLAST_FLAG)
{
if(servoBallast.readMicroseconds() != unBallastIn)
{
servoBallast.writeMicroseconds(unBallastIn);
}
}

if(bUpdateFlags& KNOB_FLAG)
{
if(servoKnob.readMicroseconds() != unKnobIn)
{
servoKnob.writeMicroseconds(unKnobIn);
}
}

if(bUpdateFlags& AUX_FLAG)
{
if(servoAux.readMicroseconds() != unAuxIn)
{
servoAux.writeMicroseconds(unAuxIn);
}
}

if(bUpdateFlags& HYDRO_FLAG)
{
if(servoHydro.readMicroseconds() != unHydroIn)
{
servoHydro.writeMicroseconds(unHydroIn);
}
}

bUpdateFlags = 0;
}

// simple interrupt service routine
void calcBallast()
{
// if the pin is high, its a rising edge of the signal pulse, so lets record its value
if(digitalRead(BALLAST_IN_PIN) == HIGH)
{
ulBallastStart = 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.
unBallastInShared = (uint16_t)(micros() - ulBallastStart);
// use set the throttle flag to indicate that a new throttle signal has been received
bUpdateFlagsShared |= BALLAST_FLAG;
}
}

void calcKnob()
{
if(digitalRead(KNOB_IN_PIN) == HIGH)
{
ulKnobStart = micros();
}
else
{
unKnobInShared = (uint16_t)(micros() - ulKnobStart);
bUpdateFlagsShared |= KNOB_FLAG;
}
}

void calcAux()
{
if(digitalRead(AUX_IN_PIN) == HIGH)
{
ulAuxStart = micros();
}
else
{
unAuxInShared = (uint16_t)(micros() - ulAuxStart);
bUpdateFlagsShared |= AUX_FLAG;
}
}

void calcHydro()
{
if(digitalRead(HYDRO_IN_PIN) == HIGH)
{
ulHydroStart = micros();
}
else
{
unHydroInShared = (uint16_t)(micros() - ulHydroStart);
bUpdateFlagsShared |= HYDRO_FLAG;
}
}

Pls note the sketch below

PLEASE read the How To Use the Forum and put your code between code tags - it makes it much easier to read, especially when the code is long.

...R

Looks like you're missing some digitalWrite calls here:

(PISTON_DIVE_PIN, HIGH);
(PISTON_SURFACE_PIN, HIGH);
(PUMP_DIVE_PIN, HIGH);
(PUMP_SURFACE_PIN, HIGH);
(TRIM_TO_BOW_PIN, HIGH);
(TRIM_TO_STERN_PIN, HIGH);
(WATER_GUN_PIN, HIGH);