This is my mower I converted to electric. You’ll notice it doesn’t actually cut anything. That’s because I’m waiting on the 4th motor controller to arrive after frying the first three.
For the 4 wires out of the back. You need a 4 pole speakon connector and socket. Twist lock. Can be removed while powered no shorting out.
well done and thank you.
Dear Sir, I'm watching your Radio controlled reelmower. You added this video in august 2015. Have you done some improvement on this mower since ? I'd like to know more about it. I am from France in Europe. Here is my email address : laviefrancois@gmail.com send me a mail. I have more questions. Happy Thanksgiving.
all you guys looking for source code and problems with RC PPM etc. just get a dimension engineering sabertooth motor controller!!! it is exactly what they are meant for and your could even hook an RC Gyro in line at the RC receiver…
This is the source code minus libraries. Please understand I have no formal education in programming, and that I have looked at this code in a year. To explain how the code flows would take me just as much effort as you. But it should follow the basic – where are we, what changes have happened, is there new commands, and apply motor power.
void setup() { Serial.begin(9600); Wire.begin(); // Join the I2C bus (we will be acting as master) diabloAddress = mainmotors; //set address of diablo board DiabloSetEpoIgnore(true); //we will not be using an EPO, instead a relay controlled power DiabloResetEpo(); //precautionary statement to reset the EPO
if (Usb.Init() == -1) { //start the usb library. this code is c/p from example code Serial.println(F("rnOSC did not start")); while (1); //halt } Serial.println(F("rnXbox Wireless Receiver Library Started")); while (!DiabloGetCommsFailsafe()) { //Set the diablo to turn off on LOS for 1/4sec DiabloSetCommsFailsafe(true); } if (!bno.begin()) { /* There was a problem detecting the BNO055 … check your connections */ Serial.print("Ooops, no BNO055 detected … Check your wiring or I2C ADDR!"); while (1); }
bno.setExtCrystalUse(true); SetSampleTime(20); //refresh rate of imu sampling 1000/20 Hz SetOutputLimits(-0.50, 0.50); //the limits of power for the pid control setkp = 0.005; setki = 0.005; setkd = 0.000; SetTunings(setkp, setki, setkd); SetControllerDirection(DIRECT); //it's either forward or backwards SetMode(MANUAL); //Manual means no processing. will change later
Usb.Task(); //best attempt to set led on controller. sometimes it doesn't work Xbox.setLedOn(LED1, xbcontNum); }
if (Output_X > outMax) Output_X = outMax; else if (Output_X < outMin) Output_X = outMin;
if (ITerm > outMax) ITerm = outMax; else if (ITerm < outMin) ITerm = outMin; } void SetMode(int Mode) { bool newAuto = (Mode == AUTOMATIC); if (newAuto == !inAuto) { /we just went from manual to auto/ Initialize(); } inAuto = newAuto; } void Initialize() { lastInput = Input_X; ITerm = Output_X; if (ITerm > outMax) ITerm = outMax; else if (ITerm < outMin) ITerm = outMin; } void SetControllerDirection(int Direction) { controllerDirection = Direction; }
One of few RC mowers with gyro input. Would you be able to share the sourcecode? My plan is to use an arduino to take input from a normal R/C source and then adjust heading with the gyro input from BNO055 .
Hello Project! Looks awesome. I was wondering if you have a parts list/build pictures and links from where you bought all these stuff.
DIY projects usually is a success when you have the right parts.. I plan to make it more autonomous by adding GPS.. But that's a long shot.. I have a trasnmitter and reciever which I plan to use..
I do have the fiskars lawn mower and gathering all the items that I need to buy and estimating the cost.
Impressive design James any change you could send me the Electronic Schematics
how strong is the motors on that mower can you drive on it and drive up hills?
Awesome
Could you please make the PID motor control code available? I am looking for some PID code for controlling "skid-steer" style robot, and I can't seem to find a suitable example for a newbie like me. By the way… Cytron make amazing motor controllers! I am running two, MD10c r3 controllers with out even as much as a hiccup. http://www.cytron.com.my/p-md10c
Really nice project. Congrats.
I was wondering if you could tell me the range you're achieving with the xbox controller? Also, what's the battery life of your specs?
For the 4 wires out of the back. You need a 4 pole speakon connector and socket. Twist lock. Can be removed while powered no shorting out.
well done and thank you.
Dear Sir, I'm watching your Radio controlled reelmower. You added this video in august 2015. Have you done some improvement on this mower since ? I'd like to know more about it. I am from France in Europe. Here is my email address :
laviefrancois@gmail.com
send me a mail. I have more questions. Happy Thanksgiving.
all you guys looking for source code and problems with RC PPM etc. just get a dimension engineering sabertooth motor controller!!! it is exactly what they are meant for and your could even hook an RC Gyro in line at the RC receiver…
This is the source code minus libraries. Please understand I have no formal education in programming, and that I have looked at this code in a year. To explain how the code flows would take me just as much effort as you. But it should follow the basic – where are we, what changes have happened, is there new commands, and apply motor power.
#include <Adafruit_Sensor.h>
#include <Adafruit_BNO055.h>
#include <XBOXRECV.h>
#include <Usb.h>
#include <Wire.h>
#include <Diablo.h>
#include <SPI.h>
const int mainRefreshRate = 10, posRefresh = 10;
int mainmotors = 0x37;
float expcont = 2;
int BufferPOS = 0;
int bufferLength = 9;
int bladeBuffer[10];
uint8_t xbcontNum = 0;
int xbl = -32768;
int xbu = 32767;
float contX = 0.0;
float contY = 0.0;
float contR = 0;
unsigned long previoustimeMain, previoustimePID;
bool run = true;
float turnRate = .20; //in deg per ms
float lastX, lastY;
USB Usb;
XBOXRECV Xbox(&Usb);
//pid variables
unsigned long lastTime, lastTimepos;
float Input_X, Output_X, Setpoint;
double ITerm, lastInput;
double kp, ki, kd;
double setkp, setki, setkd;
int SampleTime = 10;
double outMin, outMax;
bool inAuto = false;
#define MANUAL 0
#define AUTOMATIC 1
#define DIRECT 0
#define REVERSE 1
int controllerDirection = DIRECT;
int pwmPin = 3;
#define BNO055_SAMPLERATE_DELAY_MS (10)
Adafruit_BNO055 bno = Adafruit_BNO055();
void setup()
{
Serial.begin(9600);
Wire.begin(); // Join the I2C bus (we will be acting as master)
diabloAddress = mainmotors; //set address of diablo board
DiabloSetEpoIgnore(true); //we will not be using an EPO, instead a relay controlled power
DiabloResetEpo(); //precautionary statement to reset the EPO
pinMode(pwmPin, OUTPUT);
analogWrite(pwmPin, 255);
if (Usb.Init() == -1) { //start the usb library. this code is c/p from example code
Serial.println(F("rnOSC did not start"));
while (1); //halt
}
Serial.println(F("rnXbox Wireless Receiver Library Started"));
while (!DiabloGetCommsFailsafe()) { //Set the diablo to turn off on LOS for 1/4sec
DiabloSetCommsFailsafe(true);
}
if (!bno.begin())
{
/* There was a problem detecting the BNO055 … check your connections */
Serial.print("Ooops, no BNO055 detected … Check your wiring or I2C ADDR!");
while (1);
}
bno.setExtCrystalUse(true);
SetSampleTime(20); //refresh rate of imu sampling 1000/20 Hz
SetOutputLimits(-0.50, 0.50); //the limits of power for the pid control
setkp = 0.005; setki = 0.005; setkd = 0.000;
SetTunings(setkp, setki, setkd);
SetControllerDirection(DIRECT); //it's either forward or backwards
SetMode(MANUAL); //Manual means no processing. will change later
Usb.Task(); //best attempt to set led on controller. sometimes it doesn't work
Xbox.setLedOn(LED1, xbcontNum);
}
void loop()
{
Compute();
CalcPosition();
int mainCycleTime = millis() – previoustimeMain;
if (mainCycleTime > mainRefreshRate) {
previoustimeMain = millis();
Usb.Task();
if (run && Xbox.XboxReceiverConnected) {
if (Xbox.Xbox360Connected[xbcontNum]) {
if (Xbox.getButtonClick(BACK, xbcontNum))
{
inAuto = false;
while (DiabloGetMotor1 || DiabloGetMotor2) {
DiabloSetMotors(0);
DiabloMotorsOff();
}
run = false; return;
}
if (Xbox.getButtonClick(R1, xbcontNum)) {
if (!inAuto) {
inAuto = true;Input_X = 0; Output_X = 0; Setpoint = 0;
Initialize();
Xbox.setLedOn(LED2, xbcontNum);
}
else {
inAuto = false;
//contX = 0;
//contY = 0;
Xbox.setLedOn(LED1, xbcontNum);
}
}
if (Xbox.getButtonClick(UP, xbcontNum)) {
setkp += 0.0005;
SetTunings(setkp, setki, setkd);
}
if (Xbox.getButtonClick(DOWN, xbcontNum)) {
if (setkp < 0.0005) { setkp = 0.0; }
else { setkp -= 0.0005;}
SetTunings(setkp, setki, setkd);
}
if (Xbox.getButtonClick(Y, xbcontNum)) {
setki += 0.0005;
SetTunings(setkp, setki, setkd);
}
if (Xbox.getButtonClick(X, xbcontNum)) {
if (setki < 0.0005) { setki = 0.0; }
else { setki -= 0.0005; }
SetTunings(setkp, setki, setkd);
}
if (Xbox.getButtonClick(B, xbcontNum)) {
setkd += 0.0001;
SetTunings(setkp, setki, setkd);
}
if (Xbox.getButtonClick(A, xbcontNum)) {
if (setkd < 0.0001) { setkd = 0.00000; }
else { setkd -= 0.0001; }
SetTunings(setkp, setki, setkd);
}
int trigR;
if (Xbox.getButtonPress(L2, xbcontNum) > 100 && Xbox.getButtonPress(R2, xbcontNum) > 100) {
trigR = 255;
}
else {
trigR = 0;
}
if (trigR > contR + 4) { contR += 4; }
else if (trigR < contR – 8) { contR -= 8; }
else { contR = trigR; }
setBlades(contR);
float newX = ((float)Xbox.getAnalogHat(RightHatX, xbcontNum)/32768.0);
float newY = ((float)Xbox.getAnalogHat(LeftHatY, xbcontNum)/32768.0);
if (newX > contX + 0.15) { contX += 0.15; }
else if (newX < contX – 0.15) { contX -= 0.15; }
else {contX = newX;}
if (newY > contY + 0.05) { contY += 0.05; }
else if (newY < contY – 0.05) { contY -= 0.05; }
else {contY = newY; }
float motX = deadzone(contX) * 0.50;
float motY = deadzone(contY) * 0.60;
if (inAuto) {
Setpoint += turnRate * mainCycleTime * contX;
setMotors(Output_X, contY);
}
else { setMotors(motX, motY); }
}
}
else { DiabloSetMotors(0); DiabloMotorsOff; setBlades(0); }
}
}
/*float buffersum(int value, int buffer[]) {
int BufferSum;
buffer[BufferPOS] = value;
for (int index = 0; index < bufferLength; index++) {
BufferSum += buffer[index];
}
return (int)(BufferSum/bufferLength);
}*/
float deadzone(float d) {
float x;
if (d > 0.10) {
x = pow((d – 0.10)*(10/9), expcont);
}
else if (d < -0.10) {
x = -1 * pow((d + 0.10)*(10/9), expcont);
}
else { x = 0; }
return x;
}
void setMotors(float x, float y) {
float RplusL = (1 – abs(x)) * y + y;
float RminusL = (1 – abs(y)) * x + x;
float rightPower = (RplusL – RminusL) / 2;
float leftPower = (RplusL + RminusL) / 2;
DiabloSetMotor1((int) (leftPower * 255));
DiabloSetMotor2((int) (rightPower * 255));
}
void setBlades(int b) {
b = 255 – b;
analogWrite(pwmPin, b);
}
/*void advanceindex() {
BufferPOS += 1;
if (BufferPOS > bufferLength) { BufferPOS = 0; }
}*/
void CalcPosition() {
unsigned long nowpos = micros();
int timeChangepos = (nowpos – lastTimepos);
lastTimepos = nowpos;
if (timeChangepos > posRefresh*1000) {
imu::Vector<3> euler = bno.getVector(Adafruit_BNO055::VECTOR_GYROSCOPE);
Input_X += euler.z() * -57.2957795 * (float)(timeChangepos/1000000.0);
}
}
void Compute()
{
if (!inAuto) return;
unsigned long now = millis();
int timeChange = (now – lastTime);
if (timeChange >= SampleTime)
{
/Compute all the working error variables/
double error = Setpoint – Input_X;
ITerm += (ki * error);
if (ITerm > outMax*0.25) ITerm = outMax*0.25;
else if (ITerm < outMin*0.25) ITerm = outMin*0.25;
double dInput = (Input_X – lastInput);
/Compute PID Output_X/
Output_X = kp * error + ITerm – kd * dInput;
if (Output_X > outMax) Output_X = outMax;
else if (Output_X < outMin) Output_X = outMin;
/Remember some variables for next time/
lastInput = Input_X;
lastTime = now;
}
}
void SetTunings(double Kp, double Ki, double Kd)
{
if (Kp<0 || Ki<0 || Kd<0) return;
double SampleTimeInSec = ((double)SampleTime) / 1000;
kp = Kp;
ki = Ki * SampleTimeInSec;
kd = Kd / SampleTimeInSec;
if (controllerDirection == REVERSE)
{
kp = (0 – kp);
ki = (0 – ki);
kd = (0 – kd);
}
}
void SetSampleTime(int NewSampleTime)
{
if (NewSampleTime > 0)
{
double ratio = (double)NewSampleTime
/ (double)SampleTime;
ki *= ratio;
kd /= ratio;
SampleTime = (unsigned long)NewSampleTime;
}
}
void SetOutputLimits(double Min, double Max)
{
if (Min > Max) return;
outMin = Min;
outMax = Max;
if (Output_X > outMax) Output_X = outMax;
else if (Output_X < outMin) Output_X = outMin;
if (ITerm > outMax) ITerm = outMax;
else if (ITerm < outMin) ITerm = outMin;
}
void SetMode(int Mode)
{
bool newAuto = (Mode == AUTOMATIC);
if (newAuto == !inAuto)
{ /we just went from manual to auto/
Initialize();
}
inAuto = newAuto;
}
void Initialize()
{
lastInput = Input_X;
ITerm = Output_X;
if (ITerm > outMax) ITerm = outMax;
else if (ITerm < outMin) ITerm = outMin;
}
void SetControllerDirection(int Direction)
{
controllerDirection = Direction;
}
One of few RC mowers with gyro input. Would you be able to share the sourcecode? My plan is to use an arduino to take input from a normal R/C source and then adjust heading with the gyro input from BNO055 .
Hello Project! Looks awesome. I was wondering if you have a parts list/build pictures and links from where you bought all these stuff.
DIY projects usually is a success when you have the right parts.. I plan to make it more autonomous by adding GPS.. But that's a long shot.. I have a trasnmitter and reciever which I plan to use..
I do have the fiskars lawn mower and gathering all the items that I need to buy and estimating the cost.
Impressive design James any change you could send me the Electronic Schematics
how strong is the motors on that mower can you drive on it and drive up hills?
Awesome
Could you please make the PID motor control code available? I am looking for some PID code for controlling "skid-steer" style robot, and I can't seem to find a suitable example for a newbie like me. By the way… Cytron make amazing motor controllers! I am running two, MD10c r3 controllers with out even as much as a hiccup. http://www.cytron.com.my/p-md10c
Really nice project. Congrats.
I was wondering if you could tell me the range you're achieving with the xbox controller?
Also, what's the battery life of your specs?
Thanks.