Skip to content

Instantly share code, notes, and snippets.

@ShawnHymel
Created August 24, 2017 15:17
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 8 You must be signed in to fork a gist
  • Save ShawnHymel/ccc28335978d5d5b2ce70a2a9f6935f4 to your computer and use it in GitHub Desktop.
Save ShawnHymel/ccc28335978d5d5b2ce70a2a9f6935f4 to your computer and use it in GitHub Desktop.
Mixing 2 channels from an RC hobby controller for arcade drive
/**
* Two Channel Receiver
* Author: Shawn Hymel (SparkFun Electronics)
* Date: Aug 24, 2017
*
* Connect a TB6612FNG and RC (PWM) receiver to the Arduino.
* Mixes two channels for arcade drive.
*
* This code is beerware; if you see me (or any other SparkFun
* employee) at the local, and you've found our code helpful,
* please buy us a round!
* Distributed as-is; no warranty is given.
*/
// Controller pins
const int CH_1_PIN = 10;
const int CH_2_PIN = 11;
// Motor driver pins
const int STBY_PIN = 9;
const int AIN1_PIN = 2;
const int AIN2_PIN = 4;
const int APWM_PIN = 5;
const int BIN1_PIN = 7;
const int BIN2_PIN = 8;
const int BPWM_PIN = 6;
// Parameters
const int deadzone = 20; // Anything between -20 and 20 is stop
void setup() {
// Configure pins
pinMode(STBY_PIN, OUTPUT);
pinMode(AIN1_PIN, OUTPUT);
pinMode(AIN2_PIN, OUTPUT);
pinMode(APWM_PIN, OUTPUT);
pinMode(BIN1_PIN, OUTPUT);
pinMode(BIN2_PIN, OUTPUT);
pinMode(BPWM_PIN, OUTPUT);
// Enable motor driver
digitalWrite(STBY_PIN, HIGH);
}
void loop() {
// Read pulse width from receiver
int y = pulseIn(CH_2_PIN, HIGH, 25000);
int x = pulseIn(CH_1_PIN, HIGH, 25000);
// Convert to PWM value (-255 to 255)
y = pulseToPWM(y);
x = pulseToPWM(x);
// Mix for arcade drive
int left = y + x;
int right = y - x;
// Drive motor
drive(left, right);
delay(5);
}
// Positive for forward, negative for reverse
void drive(int speed_a, int speed_b) {
// Limit speed between -255 and 255
speed_a = constrain(speed_a, -255, 255);
speed_b = constrain(speed_b, -255, 255);
// Set direction for motor A
if ( speed_a == 0 ) {
digitalWrite(AIN1_PIN, LOW);
digitalWrite(AIN2_PIN, LOW);
} else if ( speed_a > 0 ) {
digitalWrite(AIN1_PIN, HIGH);
digitalWrite(AIN2_PIN, LOW);
} else {
digitalWrite(AIN1_PIN, LOW);
digitalWrite(AIN2_PIN, HIGH);
}
// Set direction for motor B
if ( speed_b == 0 ) {
digitalWrite(BIN1_PIN, LOW);
digitalWrite(BIN2_PIN, LOW);
} else if ( speed_b > 0 ) {
digitalWrite(BIN1_PIN, HIGH);
digitalWrite(BIN2_PIN, LOW);
} else {
digitalWrite(BIN1_PIN, LOW);
digitalWrite(BIN2_PIN, HIGH);
}
// Set speed
analogWrite(APWM_PIN, abs(speed_a));
analogWrite(BPWM_PIN, abs(speed_b));
}
// Convert RC pulse value to motor PWM value
int pulseToPWM(int pulse) {
// If we're receiving numbers, convert them to motor PWM
if ( pulse > 1000 ) {
pulse = map(pulse, 1000, 2000, -500, 500);
pulse = constrain(pulse, -255, 255);
} else {
pulse = 0;
}
// Anything in deadzone should stop the motor
if ( abs(pulse) <= deadzone ) {
pulse = 0;
}
return pulse;
}
@jeevan-git
Copy link

Hello there I am using Arduino with RC FS-I6 and want to control the speed.....and tried your code but...confused where to connect APWM and BPWM please provide a complete picture so I can connect it.......

@SuwinMinura
Copy link

the code isnt working when upload it to my rover

@ShawnHymel
Copy link
Author

The code should still work. Double check your wiring and make sure you're using a PWM receiver as shown in the video. https://youtu.be/Bx0y1qyLHQ4

@SuwinMinura
Copy link

SuwinMinura commented Oct 17, 2019 via email

@Majeki
Copy link

Majeki commented Jan 28, 2020

Hello, I am having a problem where only one channel works at a time. Once I unplug one of the channels then the other one that wasn't working starts to work. So currently I can only move forward and Backward, but can turn left or right, but once I unplug the 'Y' channel then I suddenly can turn left or right. I am not sure whats the problem. Please help, thank you.

@SuwinMinura
Copy link

SuwinMinura commented Jan 29, 2020 via email

@ShawnHymel
Copy link
Author

I don't have the hardware to test this anymore, as this was a project at my old job. Do you have access to an oscilloscope? Could you measure the output PWM signal of both channels to see if it's receiving it correctly? That will tell you if it's a problem with the receiver or the Arduino.

@Vishal01Mehra
Copy link

I tried the code and only one channel is working.

@Majeki
Copy link

Majeki commented Feb 1, 2020

Hi Shawn, thanks for the reply. I don't have an oscilloscope but I can access it from our local Makers Station. I have tested the pulse through the Serial Monitor and its consistent_( Ch_1 = [1001 - 1912], & Ch_2 = [900 - 1800])_. I have also tried different Arduino Uno boards and TB6612FNG but the results its still the same. The only thing I haven't tried is using the Pro Mini board (because it doesn't want to upload the program - connect through arduino Uno). I don't know the solution to it.

@ShawnHymel
Copy link
Author

If you can access the scope, I would probe both channels at the same time to see if they're receiving data. Then connect them to the Arduino while still probing them--can you still see data on both channels on the scope?

@Vishal01Mehra
Copy link

I eventually end up using Digital pin 9 and 10 for the RC inputs and its working fine now. Though they all belong to PortB, this problem seems to be strange. PWM signal is present on pin 11 (confirmed by hooking up a digital Servo). I went through the datasheet of atmega328p but didn't find any clue. It's something related to timers and the library. Will dig more into the PulseIn function. I will update if I find something interesting. Thank you for the response.

@ShawnHymel
Copy link
Author

From what I can tell, pulseIn() just records the difference between detected edges on any pin using microsecondsToClockCycles(). There's a discussion of the source code here. Are you trying this on a 328p-based Arduino and using pins 10 and 11? I don't think the pins matter based on what I saw, but they might.

Also, you may want to look at implementing your own puleseIn() function, as the Arduino one can be off sometimes. This discussion offers a good start on how to do that: https://arduino.stackexchange.com/questions/28816/how-can-i-replace-pulsein-with-interrupts

@Basilicum
Copy link

Hi, have a similar problem as some mentioned in here.
Only 1 of 2 Channels will actually be read by the Arduino at once that goes for 1/2 and 3/4. So if 1/2 are plugged in, it might only read 2. Until I unplug 2 and then It'll read 1.
Its really weird and irritating since I really need the Arcade Drive for my project. :/

@Juniper82
Copy link

Juniper82 commented Mar 7, 2020

For anyone having an issue with only one channel working I have a solution. Add a delay(5); between reading the first and second channel. I think I have a delay(5); after the two pulsein() and the drive() and its working pretty sweet.

@srfngdrmmr
Copy link

Shawn,
I am very new to robotics (and coding), while I am trying to do some on my own, it has helped to grab some code as examples. I did get my Black Gladiator Tank Chassis to run (albeit not well) with a L298. I am now trying to use an L293D Motor Shield but can't quite figure out how to change your code to work. I've added the library and statements for the two motors but I'm lost after that. Any suggestions. There is some code for L293D but it's very complicated and I couldn't get it to work. Also there is a Bluetooth version but I would like to use the FS-i16B that I bought. Thank you in advance.

@ShawnHymel
Copy link
Author

@srfngdrmmr If it's the Adafruit shield, I recommend starting with their example code: https://learn.adafruit.com/adafruit-motor-shield?view=all#use-it

@srfngdrmmr
Copy link

@srfngdrmmr If it's the Adafruit shield, I recommend starting with their example code: https://learn.adafruit.com/adafruit-motor-shield?view=all#use-it

Shawn,
Thank you so much for your reply. Yes I had reviewed that page. It was only after posting that I realized that you were presenting for "Sparkfun". So I guess asking about Adafruit was "not the best form". My issue lies in getting Arrays, PWM, and the Motor Shield to play nice with each other. Arcade Joystick is just a pleasant afterthought. I am back to the drawing board and trying again. But thank you for your wonderful videos.

@ShawnHymel
Copy link
Author

@srfngdrmmr No worries, and thank you for the compliments about the videos! I no longer work for SparkFun, so I'm happy to recommend products and tutorials for whatever company seems to have the best offering :)

@Luke8826
Copy link

Please help, I got this code to turn on 1 motor when I push the controller forward but it will turn on the 2nd in reverse and at full speed it will jolt every 500ms intervals
left and right do nothing
Thankyou in advance
20210423_134453
20210423_134412
20210423_134419
20210423_134441

@ShawnHymel
Copy link
Author

@Luke8826 Unfortunately, I'm not familiar with any of those components that you have shown, so I don't think I'll be able to help much. I think your best bet is to try 1 channel at a time to see how it behaves. If you have an oscilloscope, it can help you see what the output of each channel looks like.

My guess (looking at the receiver) is that your receiver is a PPM output whereas my code works with PWM output (which is different). You would need to rewrite the code to work with PPM output, as the pulse timing is very different.

@Teamkiss1
Copy link

I was having the same problem with the Arduino only reading One receiver channel. The other channel always returned 0. After a lot of struggling I found that if I changed the pulseIn commands delay values the Arduino started reading both channels.
The original Commands look like this
int x = pulseIn(CH_1_PIN, HIGH, 25000); l changed the value from 25000 to 30000 in both lines of code and the Arduino could then read both channels. You may have to try different values depending on which Arduino board you are using. I am using the Mega 2560...

@neoxic
Copy link

neoxic commented Dec 3, 2021

I occasionally stumbled across this gist. For those who still struggle with Arduino, please take a look at the following project:

https://github.com/neoxic/ATmega-RC-PWM-Mixer

It's a robust interrupt-driven bidirectional 2-channel PWM motor controller firmware that can be used either with standard RC signals or iBUS (FlySky).

For those in favour of STM8 instead, take a look at a similar project:

https://github.com/neoxic/STM8-RC-PWM-Mixer

Best wishes to everybody.

@kiki2op113
Copy link

I eventually end up using Digital pin 9 and 10 for the RC inputs and its working fine now. Though they all belong to PortB, this problem seems to be strange. PWM signal is present on pin 11 (confirmed by hooking up a digital Servo). I went through the datasheet of atmega328p but didn't find any clue. It's something related to timers and the library. Will dig more into the PulseIn function. I will update if I find something interesting. Thank you for the response.

hi @Vishal

I eventually end up using Digital pin 9 and 10 for the RC inputs and its working fine now. Though they all belong to PortB, this problem seems to be strange. PWM signal is present on pin 11 (confirmed by hooking up a digital Servo). I went through the datasheet of atmega328p but didn't find any clue. It's something related to timers and the library. Will dig more into the PulseIn function. I will update if I find something interesting. Thank you for the response.

can u send code of yours?

@kiki2op113
Copy link

For anyone having an issue with only one channel working I have a solution. Add a delay(5); between reading the first and second channel. I think I have a delay(5); after the two pulsein() and the drive() and its working pretty sweet.

pls send the code for arduino uno also

@getgray
Copy link

getgray commented Apr 16, 2023

Shawn/anyone: Why the map from -500 to 500? Why not map -255-255? It seems that you'd only get half the range of the joystick in response. Half of the PWM input range would always map above 255 and get truncated by constrain. Grateful to understand what I'm misunderstanding here.
re:
pulse = map(pulse, 1000, 2000, -500, 500);
pulse = constrain(pulse, -255, 255);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment