Basic tank-style drive robot control firmware for Freescale FRDM-K64F. Controls motors on a Dual-Full-H-Bridge with EN, like DBH-1x series, from Bluetooth serial commands

Dependencies:   mbed

Attic/MotorDriveDBH1.h

Committer:
Mr_What
Date:
2015-08-16
Revision:
4:7620d21baef3
Parent:
1:23d0a615756a

File content as of revision 4:7620d21baef3:

/*

Motor driver for DBH-1A/B/C series Motor controler (aliexpress, WINGXINE).
This controller has two inputs for each H-Bridge, and an enable pin,
and a current-sense.
It also provides a 5v-ish output appropriate for running the MCU

It was found that motors seemed to run a bit faster and more efficient
with PWM on the Enable.  However, since spec claims only 0-98% PWM,
We still wish to PWM the direction enable.  There might be a charge
pump someplace that needs some down time.

It seems to have a hard brake when both inputs set to 0, and enabled.
However, it does seem to coast when enable is 0, inputs don't matter in this state.

The goal of this driver is to PWM (mostly) EN when driving, but
hard-brake on speed 0 and speed 0 transitions.

*/

// Don't allow immediate change of direction.
// let it "brake" for a little bit before starting in other direction
#define MOTOR_STOP  0
#define MOTOR_2STOP 1     // 1's is transition state indicator bit
#define MOTOR_FWD   4
#define MOTOR_REV   8
#define MOTOR_2FWD  5
#define MOTOR_2REV  9

#ifndef ABS
#define ABS(x)  (((x)<0)?(-(x)):(x))
#endif

// ----------- Global state classes:
#include "MotorDriveBase.h"
class MotorDrive : public MotorDriveBase
{
protected:
  static inline float getPWM(float pwm)
  {
    pwm = ABS(pwm);
    if (pwm >  1.0f) return(1.0f);
    if (pwm <= 0.0f) return(0.0f);
    return(pwm);
  }

  void setReverse(bool rev) // true==set reverse direction, false==forward
  {
    if (rev)
      {
        //digitalWrite(Pin.IN2,    0   );
        //analogWrite( Pin.IN1, _maxPWM);
        IN2.write(0.0f);
        IN1.write(_maxPWM);
      }
    else
      {
        //digitalWrite(Pin.IN1,    0   );
        //analogWrite( Pin.IN2, _maxPWM);
        IN1.write(0.0f);
        IN2.write(_maxPWM);
      }
  }
  
public:
    // it does not seem to hurt to have IN1=IN2=1, but it doesn't seem
    // to do anything different/useful, so try to avoid this state.
  //struct {
    PwmOut IN1, IN2;  // forward/reverse selectors.
    PwmOut EN;        // enable pin
    AnalogIn CS;        // current sense analog input pin
  //} Pin;
  
  float _speed;  // current speed 0..1
  float _decel; // time to allow to stop, in ms / PWM frac
  unsigned char _mode;
  int _modeDoneTime;
  int _deadTime, _startupTime, _stopTime;
  float _minPWM, _maxPWM;
  int _msgCount;
  
  MotorDrive(PinName namEN, PinName namIN1, PinName namIN2, PinName namCS) :
     IN1(namIN1),
     IN2(namIN2),
     EN( namEN),
     CS( namCS)
  {
    EN.write(0.0f);
    IN2.write(0.0f);
    IN2.write(0.0f);
    
    IN1.period(0.001f);  // these can be audible.  They are just the little 98% limits the driver demands
    IN2.period(0.001f);
    EN.period(1.0f/8000.0f);  // may want this higher frequency to make it ultrasonic 
    
    _deadTime = 250; // emergency stop if no command update in this time interval
    _minPWM = 0.004f;     // Motors won't move below this level
    _maxPWM = 0.98f;     // don't go over this PWM level.  driver can't do it
    _startupTime = 5; // when starting from still, issue full power pulse this long to get motors started
    _stopTime = 3000;  // Pause at least this long after emergency stop before restarting
    _decel = 0.02f;   // frac. of fullspeed/ms decel time allowance      

    _msgCount = 11;  // issue this many diagnostic messages before going quiet

    emergencyStop();
  }
  
  void setCommandTimeout(int dt)
  {
      _deadTime = dt;
cSerial.print("\r\nDeadman timeout ");cSerial.print(_deadTime);cSerial.println(" ms");
  }

  virtual void stop()
  {
    //analogWrite(Pin.IN1, 0);
    //analogWrite(Pin.IN2, 0);
    //abalogWrite(Pin.EN , 0);
    EN.write(0.0f);
    IN1.write(0.0f);
    IN2.write(0.0f);
    EN.write(1.0f);

    int stoppingTime = (int)ABS(_speed * _decel);
if(_msgCount>0){_msgCount--;cSerial.print(stoppingTime);cSerial.println(" ms to stop.");}
    _modeDoneTime = millis() + stoppingTime;
    //speed=0;  don't clobber command in case of direction change
    _mode = MOTOR_2STOP;
  }

  virtual void emergencyStop()
  {
    cSerial.print("Emergency ");
    _msgCount = 11;  // turn on diagnostics for a few commands
    stop();
    _speed=0;
    _modeDoneTime += _stopTime;
  }
/*
  void begin(const int en, const int in1, const int in2, const int cs,
         const float de=2.0)
  {
    pinMode(en,OUTPUT);   digitalWrite(en,0);
    Pin.EN  =en;
    Pin.IN1 =in1;
    Pin.IN2 =in2;
    Pin.CS  =cs;
    _decel=de;

    pinMode(Pin.IN1,OUTPUT);  digitalWrite(Pin.IN1,0);
    pinMode(Pin.IN2,OUTPUT);  digitalWrite(Pin.IN2,0);
    
    emergencyStop();
  }
*/

  // Set speed -MAX_PWM for max reverse, MAX_PWM for max forward
  virtual void setSpeed(const float spdReq, const long t)
  {
    unsigned char prevMode = _mode;
    bool rev;
    switch(prevMode)
      {
      case MOTOR_2STOP :
if(_msgCount>0){_msgCount--;cSerial.print("2STOP-->");}
        if (t < _modeDoneTime)
          {  // make sure things are stopped
//        digitalWrite(Pin.IN1,0);
//        digitalWrite(Pin.IN2,0);
//        digitalWrite(Pin.EN ,0);
            EN.write(0.0f);
            IN1.write(0.0f);
            IN2.write(0.0f);
            EN.write(1.0f);
            return;
          }
        // done stoping, continue to STOP mode
        _mode = MOTOR_STOP;
if(_msgCount>0){_msgCount--;cSerial.println("stopped.");}
      case MOTOR_STOP :
if(_msgCount>0){_msgCount--;cSerial.print("STOP-->");}
        if (ABS(spdReq) < _minPWM) return;
        _mode = (spdReq < 0) ? MOTOR_2REV : MOTOR_2FWD;
        rev = (_mode == MOTOR_2REV);
        //digitalWrite(rev?Pin.IN1:Pin.IN2,1); // don't worry about PWM
        //digitalWrite(rev?Pin.IN2:Pin.IN1,0); // this is transistional state
        //digitalWrite(Pin.EN,1);   // hard kick to get started
        IN1.write(rev?1.0f:0.0f);
        IN2.write(rev?0.0f:1.0f);
        EN.write(1.0f);  // hard kick to get started
        _modeDoneTime = t + _startupTime;
        _speed = spdReq;
if(_msgCount>0){_msgCount--;cSerial.print("Start ");cSerial.println(rev ? "REV" : "FWD");}
        return;
      case MOTOR_FWD :
      case MOTOR_REV :
if(_msgCount>0){_msgCount--;cSerial.print(_speed);cSerial.print("-->");cSerial.println(spdReq);}
        setReverse(_mode == MOTOR_2REV);
        if (t > _modeDoneTime) { emergencyStop(); return; } // deadman expired
        if ( (ABS(spdReq) < _minPWM)  ||  // stop or change direction
            ((spdReq < 0) && (prevMode == MOTOR_FWD)) ||
            ((spdReq > 0) && (prevMode == MOTOR_REV)) )
          {
            stop();
            // set speed so that it goes to this speed after coast-down
            _speed = (ABS(spdReq) < _minPWM) ? 0 : spdReq;
            return;
          }
        _speed = spdReq;
        //analogWrite(EN,getPWM(_speed));
        EN.write(_speed);
        _modeDoneTime = t + _deadTime;
if(_msgCount>0){_msgCount--;cSerial.println(_speed);}
        return;
      case MOTOR_2REV :
      case MOTOR_2FWD :
if(_msgCount>0){_msgCount--;cSerial.print("start");cSerial.println(spdReq);}
        if (ABS(spdReq) < _minPWM)
          {
            _speed = 100;  // give it some time to decel, although just starting
            stop();
            _speed = 0;
            return;
          }
        if ( ((spdReq < 0.0f) && (_mode == MOTOR_2FWD)) ||
             ((spdReq > 0.0f) && (_mode == MOTOR_2REV)) )
          { // direction change
            _speed = 0.5f;  // give it some time to decel, although just starting
            stop();
            _speed = spdReq;  // go to this speed after coast-down period
            return;
          }
        // same direction, but speed request change
        _speed = spdReq;
        if (t >= _modeDoneTime)
          {
            _mode = (_speed > 0) ? MOTOR_FWD : MOTOR_REV;
            _modeDoneTime = t + _deadTime;
            setReverse(_mode == MOTOR_REV);  // make sure direction is correct
            EN.write(_speed);
if(_msgCount>0){_msgCount--;cSerial.print("Started ");cSerial.print((int)t);cSerial.print("  done ");cSerial.println(_modeDoneTime);}
          }
        return;

      default:
        cSerial.println("unrecognized state");
      }
  }

  // update state, but no new command (no deadman reset)
  // Checks if previous command is complete, and an automatic state transition
  // is needed
  virtual void update(long t)  // current time, from millis()
  {
//Serial.print(F("Update "));  Serial.println(t);
    if ((_modeDoneTime > 0xfffff000) && (t < 999))
      {  // time counter must have wrapped around
        _modeDoneTime = 0;
        cSerial.println("Clock wrap-around");
      }

    unsigned char prevMode = _mode;
    switch(prevMode)
      {
      case MOTOR_2STOP : 
      case MOTOR_STOP :
        if ((t > _modeDoneTime) && _speed)
          { // this was a temp stop in a direction change.  Command desired speed.
if(_msgCount>0){_msgCount--;cSerial.print("Restart ");cSerial.println(_speed);}
            setSpeed(_speed,t);
          }
//else Serial.println("stopped.");
        return;
      case MOTOR_FWD :
      case MOTOR_REV :
    if (t > _modeDoneTime) emergencyStop(); // deadman expired
    return;
      case MOTOR_2REV :
      case MOTOR_2FWD :
    if (t > _modeDoneTime)
          {
            //mode = (prevMode == MOTOR_2REV) ? MOTOR_REV : MOTOR_FWD;
if(_msgCount>0){_msgCount--;cSerial.println("moving");}
            setSpeed(_speed,t);
          }
    return;
      }
  }

  float getCurrent()
  {
    //return(analogRead(Pin.CS));
    return(CS.read());
  }
};