Yet Another DIY Bluetooth RC Car
Posted:
Sat Feb 07, 2015 3:38 pm
by el_es
Hi,
bought the Dagu Mini Driver and Bluetooth Module recently,
to turn an old 27MHz analog receiver RC car into a 21st century toy :) after the original control board (or the rc transmitter) broke...
I went as far as having Arduino IDE installed, I (ab)used the serial-event example program (erm sorry I'm old-fashioned, i write programs, not sketches...) and I got it going ! on usb serial AND on Bluetooth through PuTTY :)
Currently my program uses the 2 PWM motor controls to control the 2 motors: one propulsion motor, driving rear wheels, one direction motor, directly attached to the steering rack (which was a bit of my downfall but that'll come later...)
It's fun to control my car now : just write f0...ff or b0..bf to serial port for forward/backward motion (first character is direction, second is controlling the PWM in 1/16 increments ;) ) or l0..lf and r0..rf for left/right accordingly :)
Plan is to have an Android app that will do it (and use my tablet/phone accelerometer/gyroscope) to actually act as a steering wheel.
OH Yes I know there are tons of such projects on the appstore... in fact one such project actually inspired me to do this :)
So more is to come (hopefully) :) only now I need to get myself ISP programmer (or buy another mini-driver, or send the current board back to DR... for bootloader refresh) : one of the motors drew so much current in stall condition, that supply (car has space for 4xAAA cells only...) dropped and the bootloader botched itself. Oddly the arduino program still works, but cannot be reprogrammed any more :( fails verification.
For the arduino program I still need to implement safety features (like, automatic stop when lost serial contact) and un-n00b it a bit (chartoint function anybody? :) )
I'll post more when I have more :)
Re: Yet Another DIY Bluetooth RC Car
Posted:
Tue Feb 10, 2015 6:35 pm
by Alan
Hi there,
Welcome to the forum.
Your project sounds cool, any chance of some pictures?
Sorry to hear that your bootloader has got corrupted. If you can get access to another Arduino, then that's probably the easiest way to fix it. Alternatively you can also fix it with a
Raspberry Pi.
Regards
Alan
Re: Yet Another DIY Bluetooth RC Car
Posted:
Wed Feb 11, 2015 12:11 am
by el_es
Got myself this instead:
- Code: Select all
http://www.amazon.co.uk/gp/product/B00S4AO6W6?psc=1&redirect=true&ref_=oh_aui_detailpage_o00_s00
(usage of code tag intentional)
because I don't have a RaspberryPI on hand unfortunately.
Pictures are in the making
PICS!
Posted:
Wed Feb 11, 2015 8:16 pm
by el_es
the donor car, with A4 paper for scale.
with the mini-driver also for scale
bonnet off, the mindriver on cables
close-up on cavity where minidriver will be eventually fitted - the plastic nuts are from it's package, very helpful.
(gosh I wish I resized them to forum size
EDIT: I Have Resized them now...)
Re: Yet Another DIY Bluetooth RC Car
Posted:
Fri Feb 13, 2015 11:36 pm
by el_es
SerialEventDrivenCar.ino :
- Code: Select all
/*
Heavily Based on the Serial Event Arduino example
(I took the example and heavily extended it)
Is usable but lacks safety features (like fast stop or runaway prevention,
or voltage measurements or... )
Use at your own risk!
Apply reason when copying!
This code is public domain as the original was.
Car has a rear drive motor and NO SERVO,
steering motor is directly driving the steering rack,
against the steering spring,
which /could/ make it work for granularity of angle;
(only it needs at least 5 AAA batteries supply to prevent voltage from falling below 5V regulator threshold
when either motor is stalled)
(car only has space for 4)
(or maybe just a nano size boost-mode UBEC to replace linear 5v reg on board?)
(like the one for RasPI)
receives 2 char command
commands are:
F0 - FF = forward motion, in 16 steps 0-f
B0 - BF = backward motion, in 16 steps 0-f
L0 - LF = left turn in 16 steps 0-f
R0 - RF = right turn in 16 steps 0-f
S + any char = stop propulsion
SN = stop propulsion and turn off direction motor
Program accumulates 2 characters in a string, then passes them over to the
command recognition = no need for /n
commands can also be given in lower case (f0-ff,b0-bf,r0-rf, l0-lf, s_, sn)
Program echoes to serial what it has received so sender can catch if the
commands happen to desync.
(it could be happening when the command string is handled and another starts
being received, there is a window for this and I've seen it happening)
*/
String inputString = ""; // a string to hold incoming data
boolean stringComplete = false; // whether the string is received complete
int led = 13; // leftovers from tryouts
int ledstate = 0; // led initially off
int DirMotorSpeedPin = 9; // on DAGU Mini-driver DG-0813, ML PWM pin
int DirMotorDirecPin = 7; // on DAGU Mini-driver DG-0813, ML h-bridge direction
int PropMotorSpeedPin = 10; // on DAGU Mini-driver DG-0813, MR PWM pin
int PropMotorDirecPin = 8; // on DAGU Mini-driver DG-0813, MR h-bridge direction
int firstrun = 0;
//variables to hold selected PWM pin, the selected PWM's direction,
int CommandingPWM = 0;
int CommandingDir = 0;
// and what actually to write to them
int DirectionCommand = 0;
int SpeedCommand = 0;
void setup() {
pinMode(led, OUTPUT);
pinMode(DirMotorSpeedPin, OUTPUT);
pinMode(DirMotorDirecPin, OUTPUT);
pinMode(PropMotorSpeedPin, OUTPUT);
pinMode(PropMotorDirecPin, OUTPUT);
// initialize serial:
Serial.begin(9600);
// reserve 10 bytes for the inputString: // original was 200 bytes, no need for that much
inputString.reserve(10);
}
void toggle_LED() {
if (ledstate == 0) ledstate = 1; else ledstate = 0;
digitalWrite(led,ledstate);
}
int letterToPWM(char inputChar) {
// TODO : replace with 'return hexchartoint(upcase(inputChar))*17' ;
// but this may actually be quicker as it's just a lookup
switch (inputChar){
case '0' : return 0; break;
case '1' : return 17; break;
case '2' : return 34; break;
case '3' : return 51; break;
case '4' : return 68; break;
case '5' : return 85; break;
case '6' : return 102; break;
case '7' : return 119; break;
case '8' : return 136; break;
case '9' : return 153; break;
case 'a' :
case 'A' : return 170; break;
case 'b' :
case 'B' : return 187; break;
case 'c' :
case 'C' : return 204; break;
case 'd' :
case 'D' : return 221; break;
case 'e' :
case 'E' : return 238; break;
case 'f' :
case 'F' : return 255; break;
}
}
int inputstringlen = 0;
void loop() {
// when program runs first time, tell the other side we are here
// TODO: actually output 'firmware' version
if (firstrun ==0 ) {
Serial.println("Car ready");
firstrun = 1;
}
if (stringComplete) {
// echo out the captured string
Serial.println(inputString);
// look up the first character and
// select pin and speed to be changed
// the DirectionCommand in either propulsion or direction case
// here has been established experimentally :)
switch(inputString.charAt(0)) {
case 'F' :
case 'f' : { // forward propulsion
CommandingPWM = PropMotorSpeedPin;
CommandingDir = PropMotorDirecPin;
DirectionCommand = 1;
SpeedCommand = letterToPWM(inputString.charAt(1));
}
break;
case 'B' :
case 'b' : { // backward propulsion
CommandingPWM = PropMotorSpeedPin;
CommandingDir = PropMotorDirecPin;
DirectionCommand = 0;
SpeedCommand = letterToPWM(inputString.charAt(1));
}
break;
case 'R' :
case 'r' : { // right direction
CommandingPWM = DirMotorSpeedPin;
CommandingDir = DirMotorDirecPin;
DirectionCommand = 0;
SpeedCommand = letterToPWM(inputString.charAt(1));
}
break;
case 'L' :
case 'l' : { // left direction
CommandingPWM = DirMotorSpeedPin;
CommandingDir = DirMotorDirecPin;
DirectionCommand = 1;
SpeedCommand = letterToPWM(inputString.charAt(1));
}
break;
// this has compiled but bootloader refused to work all of a sudden
case 'S':
case 's':{
CommandingPWM = PropMotorSpeedPin;
CommandingDir = PropMotorDirecPin;
DirectionCommand = 0;
SpeedCommand = 0;
// TODO: enable the below to catch 'sn' case where both direction and propulsion are off
//if (inputString.charAt(1) == 'n' || inputString.charAt(1) == 'N'){
// special case, turn direction motor off
// analogWrite(DirMotorSpeedPin,0);
// digitalWrite(DirMotorDirecPin,0);
//}
}
break;
}
// use the CommandingDir pin to select the right direction output,
// and change it
digitalWrite(CommandingDir, DirectionCommand);
// use the CommandingPWM pin to select desired speed,
// and set it accordingly
analogWrite(CommandingPWM, letterToPWM(inputString.charAt(1)));
// echo the captured string out again
Serial.println(inputString);
// clear the string:
inputString = "";
inputstringlen = 0;
stringComplete = false;
}
}
/*
SerialEvent occurs whenever a new data comes in the
hardware serial RX. This routine is run between each
time loop() runs, so using delay inside loop can delay
response. Multiple bytes of data may be available.
*/
void serialEvent() {
while (Serial.available()) {
// get the new byte:
char inChar = (char)Serial.read();
// add it to the inputString:
inputString += inChar;
// increase length counter
inputstringlen += 1;
// if we accumulated 2 characters in the string,
// tell the main loop it's ready
if (inputstringlen == 2) stringComplete = true;
}
}
Re: Yet Another DIY Bluetooth RC Car
Posted:
Sun Feb 15, 2015 12:47 pm
by Alan
Wow,
Just logged onto the forum for the first time in 4 days and seen this. Thanks for the pictures, and code, it looks great, and I'm now quite tempted to get hold of an RC car to try it for myself.
It's a pity that time is in such short supply...
Alan
Re: Yet Another DIY Bluetooth RC Car
Posted:
Sun Feb 15, 2015 6:01 pm
by el_es
Yeah time is what I don't have much either
Still todo (when I receive my isp programmer):
- test the immediate stop, enable & compile & test the stop&neutrum command
- implement&test anti-runaway & proper handling of reset condition
- write android app to control
Re: Yet Another DIY Bluetooth RC Car
Posted:
Fri Feb 20, 2015 1:51 am
by el_es
New version : SerialEventDrivenCar2.ino
- Code: Select all
/*
Heavily Based on the Serial Event example
(Public domain as the original was.)
(Apply reason when copying, use at your own risk !)
Changes from version 1 :
- fix command queuing : now the serialEvent only adds characters to the end of string
and the loop() only removes them from the beginning of string
so we have primitive FIFO command buffer / queue
- use more constant declarations
- split subroutines into self-contained functions
- changed forward/backward char codes to P and T instead of F and B
(because both f and b are in the hex characters range)
- implement s as single char command
- implement v as single char command
TODO still :
- implement auto-stop / anti runaway protection
* * *
Car has a rear drive motor and NO SERVO,
forward motor is directly diving the steering rack,
the steering motor works against the steering spring,
which could make it work for granularity of angle;
receives command of 1 or 2 characters
commands are:
P0 - PF = forward motion, in 16 speeds 0-f
T0 - TF = backward motion, in 16 speeds 0-f
L0 - LF = left turn
R0 - RF = right turn
for turning command, 0-f sets steering motor speed / stall force
S = put all motions to a stop / turn both motors off
V = return version and supported commands;
here '1.0-PTRLsv' indicates fw 1.0 and PTRL commands using 2 chars, s and v using 1 char
Either Command and Speed characters can also be lower-case.
*/
String inputString = ""; // a string to hold incoming data
const int DirMotorSpeedPin = 9; // on DAGU Mini-driver DG-0813, ML PWM pin
const int DirMotorDirecPin = 7; // on DAGU Mini-driver DG-0813, ML h-bridge direction
const int PropMotorSpeedPin = 10; // on DAGU Mini-driver DG-0813, MR PWM pin
const int PropMotorDirecPin = 8; // on DAGU Mini-driver DG-0813, MR h-bridge direction
boolean firstrun = true;
int CommandingPWM = 0;
int CommandingDir = 0;
int DirectionCommand = 0;
int SpeedCommand = 0;
void setup() {
pinMode(DirMotorSpeedPin, OUTPUT);
pinMode(DirMotorDirecPin, OUTPUT);
pinMode(PropMotorSpeedPin, OUTPUT);
pinMode(PropMotorDirecPin, OUTPUT);
// initialize serial:
Serial.begin(9600);
// reserve 10 bytes for the inputString: // original was 200 bytes, no need for that much
inputString.reserve(10);
}
int letterToPWM(char inputChar) {
// TODO : replace with 'return hexchartoint(upcase(inputChar))*17' ;
if (((inputChar >= 'a') && (inputChar <= 'f'))) {
inputChar &= 0xdf; // so upcase only letters not numbers...
}
switch (inputChar) {
case '0' : return 0; break;
case '1' : return 17; break;
case '2' : return 34; break;
case '3' : return 51; break;
case '4' : return 68; break;
case '5' : return 85; break;
case '6' : return 102; break;
case '7' : return 119; break;
case '8' : return 136; break;
case '9' : return 153; break;
case 'A' : return 170; break;
case 'B' : return 187; break;
case 'C' : return 204; break;
case 'D' : return 221; break;
case 'E' : return 238; break;
case 'F' : return 255; break;
default : return -1; break; // unrecognized character
}
}
// pull out some large functions
void fullStop() {
analogWrite(DirMotorSpeedPin,0);
digitalWrite(DirMotorDirecPin,0);
analogWrite(PropMotorSpeedPin,0);
digitalWrite(PropMotorDirecPin,0);
}
boolean letterToCommand(char inputChar) {
// returns true for 2 char command, false for 1 char command
// select pin and speed to be changed
// the DirectionCommand in either propulsion or direction case
// here has been established experimentally :)
// {
boolean ret = true;
switch (inputChar & 0xdf) { // mask the 5th bit off so we get upper case char as result
case 'P' : { // forward propulsion
CommandingPWM = PropMotorSpeedPin;
CommandingDir = PropMotorDirecPin;
DirectionCommand = 1;
}
break;
case 'T' : { // backward propulsion
CommandingPWM = PropMotorSpeedPin;
CommandingDir = PropMotorDirecPin;
DirectionCommand = 0;
}
break;
case 'R' :
{ // right direction
CommandingPWM = DirMotorSpeedPin;
CommandingDir = DirMotorDirecPin;
DirectionCommand = 0;
}
break;
case 'L' :
{ // left direction
CommandingPWM = DirMotorSpeedPin;
CommandingDir = DirMotorDirecPin;
DirectionCommand = 1;
}
break;
case 'S' :
{
fullStop();
return false;
}
break;
case 'V' :
{
Serial.println("1.0-PTRLsv");
return false;
}
break;
default: {
// when the first char was not recognized, crop it
return false; // cut the main loop early
}
} // switch inputString.charAt(0)
// if we went through all that without returning earlier, return true
return ret;
}
int inputstringlen = 0;
void loop() {
// when program runs first time, tell the other side we are here
// TODO: actually output 'firmware' version
if (firstrun) {
Serial.println("Car ready");
firstrun = false;
}
if (inputString.length() >= 1) {
if (!letterToCommand( inputString.charAt(0))) {
inputstringlen = 1;
goto PRINTINPUTSTRING; // single char commands don't control motor speed
}
else inputstringlen = 2;
// having processed 1 character, have we got another in buffer?
if (inputString.length() < 2)
return; // if not, loop until it appears
// common occurence
SpeedCommand = letterToPWM(inputString.charAt(1));
if (SpeedCommand >= 0) { // speed character recognized
// use the CommandingDir pin to select the desired PWM direction output,
// and change it
digitalWrite(CommandingDir, DirectionCommand);
// use the CommandingPWM pin to select desired speed,
// and set it accordingly
analogWrite(CommandingPWM, SpeedCommand);
} // if speedcommand > 0
PRINTINPUTSTRING:
// echo the captured string out after processing
Serial.println(inputString);
// remove first inputstringlen chars
inputString.remove(0,inputstringlen);
} // if inputString.length > 1
// todo runaway protection
// todo detect no-serial
} // main loop
/*
SerialEvent occurs whenever a new data comes in the
hardware serial RX. This routine is run between each
time loop() runs, so using delay inside loop can delay
response. Multiple bytes of data may be available.
*/
void serialEvent() {
while (Serial.available()) {
// get the new byte:
char inChar = (char)Serial.read();
// add it to the inputString:
if (inputString.length() < 10)
inputString += inChar;
// the idea is, if we have too much in the queue, no point queuing more
// and this part of program is only ever adding characters to the string
// and the reader is only removing them at the beginning
// this could allow us to queue the commands
}
}
Re: Yet Another DIY Bluetooth RC Car
Posted:
Wed Apr 15, 2015 11:07 pm
by el_es
It's still alive,
I was looking around various tutorials for Android programming, wishing to replicate at least the bluetooth RFCOMM examples... then maybe tie them together with 'pseudo-analog' joystick and ultimately with the angle/position sensor. Lots of loose ends to cover and very baroque (I'm talking about Android Studio...) and totally counter-intuitive naming. But yeah the project still lives.