12 Comments

  1. 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.

  2. 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.

  3. 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…

  4. 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;
    }

  5. 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 .

  6. 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.

  7. how strong is the motors on that mower can you drive on it and drive up hills?

  8. 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

  9. 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.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.