Controlling two Hoverboard Motors with an Arduino Mega

Introduction

During the course of my Senior Design project I needed to be able to control hoverboard motors and I came across an article by someone going by the name MAD-EE. His article is for only for one motor, but they are very similar, so all credit goes to him.

Motor Controller

This RioRand motor speed controller board is designed to work with a three-phase brushless DC motor with hall sensor. This board goes for about $19 on Amazon.

RioRand ZS-Z11H 350W 60-6V

https://www.amazon.com/RioRand-6-60V-Brushless-Electric-Controller/dp/B087M2378D?th=1

Pinout / Connections

Board Pinout

Screw Terminal Connectors

The 5 pins on the left hand side are for driving the motor phases and the power supply input.

MA – Motor Phase A

     MB – Motor Phase B

     MC – Motor Phase C

     VCC – Power Supply Input 6 to 60VDC

     GND – Power Supply Return

The six pins on the right hand side are for controlling the motor speed, direction, and braking. The board has on onboard 5V 78L05 voltage regulator with a 5V output pin which is intended to be used to power the external potentiometer and to apply voltage to the brake pin through a switch or a relay. 

5V – Power output from the onboard voltage regulator.

ANALOG CTRL – Speed control input. 0 to 5V signal that can be driven from the wiper terminal of a potentiometer connected to 5V and ground, or an analog output of a Digital to Analog Converter (DAC).

GND – Ground connection

DIR – Direction control. This signal is active low, so shorting it to ground or applying a logic 0 changes  the motor direction. Leaving the pin floating or applying a logic 1 will spin the motor in the default direction.

BRAKE – Controls the motor brake. This signal is active high, so shorting it to 5V or applying a logic 1 will apply the motors brake. Leaving the pin floating or applying a logic 0 will disconnect the brake and allow the motor to spin.

STOP – Functions as an enable input for the board. This signal is active low, so shorting it to ground or applying a logic 0 will disable the drive signals. This could be considered a coast or free spin mode. Leaving the pin floating or applying a logic high will enable the boards drive signals.

Aux Control Pads and Hall Sensor Connector

The auxiliary control pads allows a user to control the speed of the user using a Pulse Width Modulated (PWM) signal and to read the speed of the motor via the SC speed pulse output interface.

S – SC: Speed pulse output interface. This outputs a pulsed signal that can be used to read the speed of the motor.

P – PWM: This pin can be used to control the motor via a Pulse Width Modulated (PWM) signal. The documentation states the frequency can be from 50Hz to 20kHz, with an amplitude of 2.5V to 5V. The typically Arduino PWM signal is either 490Hz or 980Hz so it should be no problem controlling the motor speed via this pin. The PWM Jumper must be shorted and the Speed Control Potentiometer must be adjusted to the minimum when using the PWM control pin.

PWM Jumper (J1) – The J1 jumper is used to connect the PWM control line. To use the PWM for motor control, the J1 jumper must be shorted. Also, the Speed Control Potentiometer must be adjusted to the minimum.

Hall Sensor Connector – A cable is provided with the board to connect the motors hall sensors.

GND – Black wire. Used as a ground for the hall sensors.

Hc – Green wire. Hall sensor for the motor C phase.

Hb – Yellow wire. Hall sensor for the motor B phase.

Ha – White wire. Hall sensor for the motor A phase.

5V – Red wire. Hall sensor power.

Aux Control Pads, PWM Jumper, and Hall Sensor Connector

Parts List

  • 2 Hoverboard Motors
  • Battery
  • Motor Drivers
  • Arduino
  • Jumper Wires

Wiring Diagram

This is the wiring that MAD-EE uses but he is using an Uno while I have a Mega so it will be a bit different. All of the motor wires remain the same however.

Wiring with Arduino Uno
Arduino Mega

The pins below are the ones I used for the Arduino Mega

Arduino Pins

Code

The commands are in this format:

<command>,<data>,<CR>

The supported commands are:

  • PWM_RIGHT – Changes the speed of the right motor. data = 0 – 255
    • Example: PWM_RIGHT, 127
  • PWM_LEFT – Changes the speed of the left motor. data = 0 – 255
    • Example: PWM_RIGHT, 127
  • BRAKE_RIGHT – Enables and disables the right motor brake. data = 0 or 1
    • Example: BRAKE_RIGHT,1
  • BRAKE_LEFT – Enables and disables the left motor brake. data = 0 or 1
    • Example: BRAKE_LEFT,1
  • DIR_RIGHT – Changes the direction of the right motor. data = 0 or 1
    • Example: DIR_RIGHT,1
  • DIR_LEFT – Changes the direction of the left motor. data = 0 or 1
    • Example: DIR_LEFT,1
// Constants
const unsigned long SPEED_TIMEOUT = 500000;       // Time used to determine wheel is not spinning
const unsigned int UPDATE_TIME = 500;             // Time used to output serial data
const unsigned int BUFFER_SIZE = 16;              // Serial receive buffer size
const double BAUD_RATE = 115200;                  // Serial port baud rate
const double WHEEL_DIAMETER_IN = 6.5;            // Motor wheel diamater (inches)
const double WHEEL_CIRCUMFERENCE_IN = 22.25;     // Motor wheel circumference (inches)
const double WHEEL_DIAMETER_CM = 16.5;           // Motor wheel diamater (centimeters)
const double WHEEL_CIRCUMFERENCE_CM = 56.5;      // Motor wheel circumference (centimeters)

// Pin Declarations
const int PIN_DIR1 = 46;      // Motor direction signal
const int PIN_BRAKE1 = 50;    // Motor brake signal (active low)
const int PIN_PWM1 = 9;      // PWM motor speed control
const int PIN_SPEED1 = 24;   // SC Speed Pulse Output from RioRand board

const int PIN_DIR2 = 34;      // Motor direction signal
const int PIN_BRAKE2 = 38;    // Motor brake signal (active low)
const int PIN_PWM2 = 44;      // PWM motor speed control
const int PIN_SPEED2 = 28;   // SC Speed Pulse Output from RioRand board


// Variables used in ReadFromSerial function
String _command = "";       // Command received in Serial read command
int _data = 0;              // Data received in Serial read command

// Variables used in ReadSpeed function
double _freq;               // Frequency of the signal on the speed pin
double _rpm;                // Wheel speed in revolutions per minute
double _mph;                // Wheel speed in miles per hour
double _kph;                // Wheel speed in kilometers per hour

// This is ran only once at startup
void setup() 
{
    // Set pin directions
    pinMode(PIN_SPEED1, INPUT);
    pinMode(PIN_PWM1, OUTPUT);
    pinMode(PIN_BRAKE1, OUTPUT);
    pinMode(PIN_DIR1, OUTPUT);
    
    pinMode(PIN_SPEED2, INPUT);
    pinMode(PIN_PWM2, OUTPUT);
    pinMode(PIN_BRAKE2, OUTPUT);
    pinMode(PIN_DIR2, OUTPUT);
    
    // Set initial pin states
    digitalWrite(PIN_BRAKE1, false);
    digitalWrite(PIN_DIR1, false);
    analogWrite(PIN_PWM1, 0);
    
    digitalWrite(PIN_BRAKE2, false);
    digitalWrite(PIN_DIR2, true);
    analogWrite(PIN_PWM2, 0);
    
    // Initialize serial port
    Serial.begin(BAUD_RATE);
    Serial.println("---- Program Started ----");
}

// This is the main program loop that runs repeatedly
void loop() 
{
    // Read serial data and set dataReceived to true if command is ready to be processed
    bool dataReceived = ReadFromSerial();

    // Process the received command if available
    if (dataReceived == true)
        ProcessCommand(_command, _data);

    // Read the speed from input pin (sets _rpm and _mph)
    ReadSpeed();

    // Outputs the speed data to the serial port 
    WriteToSerial(); 
}

// Receives string data from the serial port
// Data should be in the format <command>,<data>
// Data should be terminated with a carriage return
// Function returns true if termination character received 
bool ReadFromSerial()
{    
    // Local variables   
    static String cmdBuffer;        // Stores the received command
    static String dataBuffer;       // Stores the received data
    static bool isCommand = true;   // Flag to store received bytes in command or data buffer
    byte recByte;                   // Byte received from the serial port
    
    // Check if any new data is available, if not exit
    if (Serial.available() == false)
      return false;
    
    // Read single byte from serial port
    recByte = Serial.read();
    
    // Check if byte is termination character (carriage return)
    if (recByte == '\r')
    {
        // Save buffers to global variables
        cmdBuffer.toUpperCase();
        _command = cmdBuffer;
        _data = dataBuffer.toInt();
      
        // Write what was received back to the serial port
        Serial.print("Received: "); 
        Serial.print(_command); 
        Serial.print(",");
        Serial.println(_data);
      
        // Clear local variables
        cmdBuffer = "";
        dataBuffer = "";
        isCommand = true;
      
        return true;
    }
    
    // Check if byte is a comma which separates the command from the data
    if ((char)recByte == ',')
    {
        isCommand = false;  // Next byte will be a data byte
        return false;
    }

    // Save data to one of the receive buffers
    if (isCommand)
        cmdBuffer += (char)recByte;
    else
        dataBuffer += (char)recByte;
    
    return false;
}

// Processes the command and data, sends result to serial port
void ProcessCommand(String command, int data)
{  
    // Process SPEED command
    if (command == "PWM_RIGHT")
    {
      Serial.print("Setting right motor speed:  ");
      Serial.println(data);
      analogWrite(PIN_PWM1, data);
    }
      
    if (command == "PWM_LEFT")
    {
      Serial.print("Setting left motor speed:  ");
      Serial.println(data);
      analogWrite(PIN_PWM2, data);
    }    
    
    // Process BRAKE command
    if (command == "BRAKE_RIGHT")
    {
      Serial.print("Setting right brake:  ");
      Serial.println(data);
      digitalWrite(PIN_BRAKE1, data);
    }

    if (command == "BRAKE_LEFT")
    {
      Serial.print("Setting left brake:  ");
      Serial.println(data);
      digitalWrite(PIN_BRAKE2, data);
    }

    // Process DIR command
    if (command == "DIR_RIGHT")
    {
      Serial.print("Setting right motor direction:  ");
      Serial.println(data);
      digitalWrite(PIN_DIR1, data);
    }
    
    if (command == "DIR_LEFT")
    {
      Serial.print("Setting left motor direction:  ");
      Serial.println(data);
      digitalWrite(PIN_DIR2, data);
    }
}

// Reads the speed from the input pin and calculates RPM and MPH
// Monitors the state of the input pin and measures the time (µs) between pin transitions
void ReadSpeed()
{
    static bool lastState = false;    // Saves the last state of the speed pin
    static unsigned long last_uS;     // The time (µs) when the speed pin changes
    static unsigned long timeout_uS;  // Timer used to determine the wheel is not spinning

    // Read the current state of the input pin
    bool state = digitalRead(PIN_SPEED1);

    // Check if the pin has changed state
    if (state != lastState)
    {
      // Calculate how long has passed since last transition
      unsigned long current_uS = micros();
      unsigned long elapsed_uS = current_uS - last_uS;

      // Calculate the frequency of the input signal
      double period_uS = elapsed_uS * 2.0;
      _freq = (1 / period_uS) * 1E6;

      // Calculate the RPM
      _rpm = _freq / 45 * 60;

      // If RPM is excessively high then ignore it.
      if (_rpm > 5000) _rpm = 0;

      // Calculate the miles per hour (mph) based on the wheel diameter or circumference
      //_mph = (WHEEL_DIAMETER_IN * PI * _rpm * 60) / 63360;
      _mph = (WHEEL_CIRCUMFERENCE_IN * _rpm * 60) / 63360; 
  
      // Calculate the miles per hour (kph) based on the wheel diameter or circumference
      //_kph = (WHEEL_DIAMETER_CM * PI * _rpm * 60) / 1000;
      _kph = (WHEEL_CIRCUMFERENCE_CM * _rpm * 60) / 100000; 

      // Save the last state and next timeout time
      last_uS = current_uS;
      timeout_uS = last_uS + SPEED_TIMEOUT;
      lastState = state;
    }
    // If too long has passed then the wheel has probably stopped
    else if (micros() > timeout_uS)
    {
        _freq = 0;
        _rpm = 0;
        _mph = 0;
        _kph = 0;
        last_uS = micros();
    }
}

// Writes the RPM and MPH to the serial port at a set interval
void WriteToSerial()
{
    // Local variables
    static unsigned long updateTime;
    
    if (millis() > updateTime)
    {
        // Write data to the serial port
        Serial.print((String)"Freq:" + _freq + " ");
        Serial.print((String)"RPM:" + _rpm + " ");
        Serial.print((String)"MPH:" + _mph + " ");
        Serial.println((String)"KPH:" + _kph + " ");

        // Calculate next update time
        updateTime = millis() + UPDATE_TIME;
    }
}

Final Product

Author: Ricardo