Mantis Rover
  • 🔡Mantis Rover Documentation
  • Supported Devices
  • Assembly Guide
    • 📺Video Tutorials
    • 📰Printed Instructions
      • Wheel Assembly using Improved Coupler
    • 📺3D CAD Model
    • 📺3D Model File Download
    • 📺Packing Guide
    • 🚫General Care/Precautions
    • 🚫Charging your Rover
  • 3D Printing
    • 🐺Designing your Part (TinkerCAD)
    • 🐺MODi Adapter 3D Print
    • ⌚Printing your Part
  • PWM Control
    • 🤖PWM Controls-Scratch
    • 🚗PWM Setup-JavaScript
    • 🚗Man-In-The-Loop Obstacle Avoidance Tutorial-JavaScript
    • 🚕PWM Motor Drive-Python
  • MODI Sensor
    • Product Specification Sheet
    • Quick Start Guide
    • MODi Cart Experiment (Bonus)
    • 🤖MODi Data-Scratch
    • 🤖MODi CSV-Scratch
    • 🚗MODi LIDAR Obstacle Avoidance-JavaScript
    • 🍊MODi LIDAR Obstacle Avoidance-Scratch
  • Motor Hat Module
    • Product Specification Sheet
    • Quick Start Guide
  • 🆘Support
Powered by GitBook
On this page
  • Rover PWM Motor Drive Tutorial
  • Overview
  • Connect MTH Board
  • Mthjoystick Breakdown
  • mthjoystick.js Breakdown
  • man-in-the-loop exercise

Was this helpful?

  1. PWM Control

Man-In-The-Loop Obstacle Avoidance Tutorial-JavaScript

PreviousPWM Setup-JavaScriptNextPWM Motor Drive-Python

Last updated 1 month ago

Was this helpful?

Rover PWM Motor Drive Tutorial

Learn how to command the motors using the Motor Hat provided in your Rover kit.

This guide requires no prior programming experience and users should focus more on the concepts being introduced versus getting lost in the minutia of the scripting language.

Overview

In this guide you will learn how to manipulate and control the Rover based on the following basic concepts:

  1. How to generate PWM commands for the ROVER.

  2. Understand how the different PWM settings affect motor drive and direction.

  3. Visualize PWM concepts with an appreciation for generated outputs.

To access the program, simply follow this link .

This Rover GUI code demo contains two files:

mthjoystick.html

o This must be in the same folder as the JavaScript file

o Contains metadata about the extension and how to configure all the Gui items in the browser.

mthjoystick.js

o This module contains the underlying BLE communications.

o We will be iteratively modifying the functions specific to the Joystick buttons above.

Connect MTH Board

Click on the Bluetooth icon and make sure your device is broadcasting. Once broadcasting, click on the pair button to connect to the board.

Once you have a successful connection, the interface will report that a connection was successful.

After connecting, use the arrow keys to move the motors. Notice how the motors move as your move the commands. Press the stop button to cease all motion.

Mthjoystick Breakdown

Let's break down the contents of mthjoystick.html file. This code tells your browser how and where to place everything that you see on the screen demonstrated above. You can copy this code directly into a text editor and make sure to name it with the same convention above.

mthjoystick.html
<!doctype html>
<html>

    <head>
        <title>MTH Control Panel</title>

        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
        <link rel="stylesheet" href="https://code.getmdl.io/1.1.3/material.indigo-pink.min.css">
        <script defer src="https://code.getmdl.io/1.1.3/material.min.js"></script>

  
        <style>

body {
  background: #fff;
}

.mip-layout {
  display: flex;
  align-items: center;
  justify-content: center;
  background: #fff;
}

.mip-card {
  width: 400px;
}

.mip-pad {
  display: flex;
  align-items: center;
  flex-direction: column;
}

.mip-pad button {
  margin: 1em 0.5em;
}


#logTextArea {
  width: 950%;
  height: 150px;
  overflow: auto;
}

        </style>
    </head>

    <body>

        <div class="mip-layout mdl-layout mdl-js-layout">

            <div class="mdl-card mdl-shadow--2dp mip-card">
                <div class="mdl-card__title">
                    <h3 class="mdl-card__title-text">Mantis MTH</h3>
                </div>
                <div class="mdl-card__supporting-text">
                    Please connect and control
						
    
                    </button>
                </div>
                <div class="mdl-card__actions mdl-card--border mip-pad">

                    <button onclick="moveForward()" class="mdl-button mdl-js-button mdl-button--fab mdl-button--raised mdl-js-ripple-effect">
                        <i class="material-icons">arrow_upward</i>
                    </button>

                    <div>
                        <button onclick="turnLeft()" class="mdl-button mdl-js-button mdl-button--fab mdl-button--raised mdl-js-ripple-effect">
                            <i class="material-icons">arrow_back</i>
                        </button>

                        <button onclick="connect()" class="mdl-button mdl-js-button mdl-button--fab mdl-button--colored">
                            <i class="material-icons">bluetooth_searching</i>
                        </button>

                        <button onclick="turnRight()" class="mdl-button mdl-js-button mdl-button--fab mdl-button--raised mdl-js-ripple-effect">
                            <i class="material-icons">arrow_forward</i>
                        </button>
                    </div>

                    <button onclick="moveBackward()" class="mdl-button mdl-js-button mdl-button--fab mdl-button--raised mdl-js-ripple-effect">
                        <i class="material-icons">arrow_downward</i>
                    </button>
				

                </div>
                <div class="mdl-card__actions mdl-card--border">
                    <button onclick="stop()">
                        Stop
                    </button>     
                </div>
                <textarea id="logTextArea" readonly></textarea>
            </div>
           
        </div>
        

        <script src="mthjoystick.js"></script>
      
    </body>

</html>

For starters you can see where the buttons are called out here starting on line 1, four buttons in total and one for each direction we desire to move the Rover. There are a number of styles and types that can be used to configure these buttons for a different shape and feel in the future.

 <button onclick="moveForward()" class="mdl-button mdl-js-button mdl-button--fab mdl-button--raised mdl-js-ripple-effect">
                        <i class="material-icons">arrow_upward</i>
                    </button>

                    <div>
                        <button onclick="turnLeft()" class="mdl-button mdl-js-button mdl-button--fab mdl-button--raised mdl-js-ripple-effect">
                            <i class="material-icons">arrow_back</i>
                        </button>

                        <button onclick="connect()" class="mdl-button mdl-js-button mdl-button--fab mdl-button--colored">
                            <i class="material-icons">bluetooth_searching</i>
                        </button>

                        <button onclick="turnRight()" class="mdl-button mdl-js-button mdl-button--fab mdl-button--raised mdl-js-ripple-effect">
                            <i class="material-icons">arrow_forward</i>
                        </button>
                    </div>

                    <button onclick="moveBackward()" class="mdl-button mdl-js-button mdl-button--fab mdl-button--raised mdl-js-ripple-effect">
                        <i class="material-icons">arrow_downward</i>
                    </button>

mthjoystick.js Breakdown

The man-in-the-loop experiment calls for manipulating the drive strength of these buttons and observing the results as you maneuver around the obstacle course that you made. It is important when you learn to code that you appreciate certain aspects of the code while focusing your efforts on the specific regions of concern. All other sections in the mthjoystick.js javascript file maintain very low level details of the BLE layer that is beyond the scope of this exercise. Before proceeding further go ahead and copy this code into a text editor in its entirety and save it with the exact naming convention above.

mthjoystick.js
'use strict';
var mDev=null;
var isMTHConnected=false;
var isMonitoring=0;
var interval=null;
const originalLog = console.log;

var myCharacteristic,gattServer;
const logTextArea = document.getElementById("logTextArea");

var AccelX, AccelY, AccelZ, GyroX, GyroY, GyroZ, Distance;

function connect() {
    console.log('Scanning for MTH Device...');
    if (!navigator.bluetooth) {
        alert('Web Bluetooth is disabled! Please go to chrome://flags and enable it.');
        return;
    }


    navigator.bluetooth.requestDevice({
        filters: [{ namePrefix: "MTH" }],
        optionalServices: ['0000ffd0-0000-1000-8000-00805f9b34fb']
    })
        .then(device => {
           let deviceprefix=device.name.split("_")[0];
            mDev = new MantisDevice(deviceprefix.toLowerCase());
            // Connect to the GATT server and get the services
            device.gatt.connect().then(server => {
                // Get the Service
              server.getPrimaryService(mDev.PrimaryService)
                    .then(service => {
                        mDev.primaryServiceObj = service;
                       // console.log("got service");

                        if (mDev.InitializationChar != null) {
                            service.getCharacteristic(mDev.InitializationChar)
                                .then(initChar => {
                                   // console.log("got initialize characteristic");
                                    mDev.initializationCharObj = initChar;
                                })
                        }
                        if (mDev.MailboxChar != null) {
                            service.getCharacteristic(mDev.MailboxChar)
                                .then(mailboxChar => {
                                    //console.log("got mailbox characteristic");
                                    mDev.mailboxCharObj = mailboxChar;
                                })
                        }

                        if (mDev.CommandChar != null) {
                            service.getCharacteristic(mDev.CommandChar)
                                .then(commandChar => {
                                   // console.log("got command characteristic");
                                    mDev.commandCharObj = commandChar;
                                    isMTHConnected=true;
                                    console.log("Connected!");
                                })
                        }
                       

                    })
                    .catch(error => {
                        console.error("Failed to get services: " + error.message);
                    });
            })
                .catch(error => {
                    console.error("Error requesting device: " + error.message)
                })
        })
 
}

function writeBytesUUID(characteristic, bytes) {
    characteristic.writeValue(bytes)
        .catch(error => {
            console.error("writeBytesUUID Error writing characteristic: " + error.message);
        })
}

var conversions = (function () {

    // define private constant variables

    var TWO_TO_FIFTHTEENTH = 32768;
    var GRAV_CONST = 9.80665;
    var DEG2RAD = 0.0174532925;                     // pi/180
 
    var code2accel = function code2accel (code) {
        return (code / TWO_TO_FIFTHTEENTH) * 2 * GRAV_CONST;
    };
    var code2radsec = function code2radsec (code) {
        return (code / TWO_TO_FIFTHTEENTH) * 250 * DEG2RAD;
    };
   
    var modi_code2distance = function modi_code2distance (distADCLSByte,distADCMSByte){
 

        var distCorrected, dist;
        var modiDistanceLSB=33.3 / 65536.0;
        var m_modi=1.0426;
        var b_modi=-3.5514;
        
        var distCode = (distADCLSByte * 256) + distADCMSByte;     // Concatenate LSByte and MSByte of distance ADC code

        distCode &= ~0x0007;											 // Throw away (clear) Bits 2-0;

        dist = distCode * modiDistanceLSB * 100.0;                      // Compute distance (in cm)
        distCorrected = (dist * m_modi) + b_modi;     

        if (distCorrected < 0 || distCorrected >1000 ) { distCorrected = 0; }{                   // Truncate out of range values
            return distCorrected;										    // return corrected distance in cm
        }
    };
    // public methods/attributes
    return {
        code2accel             : code2accel,
        code2radsec            : code2radsec,
        modi_code2distance 	   : modi_code2distance
    };
})();

function onStartButtonClick() {

  //console.log('Requesting Bluetooth Device for Modi...');
  
    navigator.bluetooth.requestDevice(
        {
            filters: [{ namePrefix: "MDI" },{ namePrefix: "MODI" }],  
            optionalServices: [0xffD0]        
        })
        .then(device => {
            //console.log('> Found ' + device.name);
           // console.log('Connecting to GATT Server...');
            return device.gatt.connect();
        })
        .then(server => {
            gattServer = server;
           // console.log('Getting Service 0xffb0 - Command control...');
            return server.getPrimaryService(0xffD0);
        })
        .then(service => {
           // console.log('Getting Characteristic 0xffb5 - Command...');
            return service.getCharacteristic(0xffb5);
        })
  .catch(error => {
    console.log('Argh! ' + error);
  });
  
  
}

function sendCommand(...bytes) {
    var buffer = new ArrayBuffer(bytes.length);
    var view = new Uint8Array(buffer);
    for (let i = 0; i < bytes.length; i++) {
        view[i] = bytes[i];
    }
    return myCharacteristic.writeValue(buffer)
        .catch(err => console.log('Error when writing command! ', err));
}
function onStopButtonClick() {
  if (myCharacteristic) {
    myCharacteristic.stopNotifications()
    .then(_ => {
      console.log('> Notifications stopped');
      myCharacteristic.removeEventListener('characteristicvaluechanged',
          handleNotificationsModi);
    })
    .catch(error => {
      console.log('Argh! ' + error);
    });
  }
}
	


  function MantisDevice (devName1) {
     
   // console.log("DeviceName: " + devName1.split("_")[0]);
    switch (devName1) {
     case "mth":
        this.PrimaryService =     '0000ffd0-0000-1000-8000-00805f9b34fb';
        this.NotificationChar =   '0000ffd4-0000-1000-8000-00805f9b34fb';
        this.MailboxChar = '0000ffd1-0000-1000-8000-00805f9b34fb';
        this.CommandChar = '0000ffd2-0000-1000-8000-00805f9b34fb';
      break;
  }
  
    this.primaryServiceObj =     null;
    this.notificationCharObj =   null;
    this.initializationCharObj = null;  
    this.mailboxCharObj = null;  
    this.commandCharObj = null; 
  
  }

  var bitConverter = function bitConverter (arr, start, end) {
    var populate = function populate () {
        var data = [];
        for (var i = start; i <= end; i++) {
            (function (val) {
                data.push(arr[val]);
            })(i);
        }
        return data;
    };
    var combine = function combine (data, index) {
        if (index >= 0) {
            var tmp = data[index] << (index << 3);
            return tmp | combine(data, --index);
        } else {
            return 0;
        }
    };
    if (start >= 0 && start < arr.length && end > 0 && end < arr.length) {
        return combine(populate(), arr.length - 1);
    } else {
        throw Error('Invalid parameters to bitConverter: start: ' + start + ' end: ' + end + ' arr.length: ' + arr.length);
    }
};
/**
 * A function to treat a 16 bit integer as two's complement signed
 *
 * @param val A 16 bit integer
 * @returns {int} The parameter converted to its two's complement signed value
 */
var toSignedInt16 = function toSignedInt16 (val) {
    return (new Int16Array([val]))[0];
};

var toUInt16=function toUInt16(val) {
    return (new Uint16Array([val]))[0];
};

function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

// Override console.log to append logs to the text area
console.log = function() {
  // Convert all arguments to a single string
  const message = Array.from(arguments).map(arg => String(arg)).join(" ");

  // Append the log message to the text area
  logTextArea.value += message + "\n";
  logTextArea.scrollTop = logTextArea.scrollHeight;
  // Call the original console.log
  originalLog.apply(console, arguments);
};

function movecmd(payload_cmd, payload_data) {
    writeBytesUUID(mDev.mailboxCharObj, payload_data);

    setTimeout(function () {
        self.writeBytesUUID(mDev.commandCharObj, payload_cmd);
    //    console.log("Executed CMD after 300 MS delay");
    }, 300);//timeout
 
}
  
function moveBackward() {
  
     //both PWM's set to same direction.
     let MTR1_PWM=-40;
     let MTR2_PWM=-40;
     
 
     let  payload_data  = new Uint8Array(2);
     payload_data[0] = MTR1_PWM;
     payload_data[1] = MTR2_PWM;
     
     
     let  payload_cmd  = new Uint8Array(2);
     payload_cmd [0] = 0x05;//controls both motors
     payload_cmd [1] = 0x00;
     
     movecmd(payload_cmd,payload_data);
 
     console.log("Move Backward")
 
 }

function moveForward() {
    //both PWM's set to to the same negative direciton.
    let MTR1_PWM=40;
    let MTR2_PWM=40;
    

    let  payload_data  = new Uint8Array(2);
    payload_data[0] = MTR1_PWM;
    payload_data[1] = MTR2_PWM;
    
    
    let  payload_cmd  = new Uint8Array(2);
    payload_cmd [0] = 0x05;//controls both motors
    payload_cmd [1] = 0x00;
    
    movecmd(payload_cmd,payload_data);

    console.log("Move Forward")

}

function turnLeft() {
   //both PWM's set to opposited directions.
   let MTR1_PWM=40;
   let MTR2_PWM=-40;

   let  payload_data  = new Uint8Array(2);
   payload_data[0] = MTR1_PWM;
   payload_data[1] = MTR2_PWM;
   
   
   let  payload_cmd  = new Uint8Array(2);
   payload_cmd [0] = 0x05;//controls both motors
   payload_cmd [1] = 0x00;
   
   movecmd(payload_cmd,payload_data);

   console.log("Turn Left")

}

function turnRight() {
    
   //both PWM's set to opposite directions.
    let MTR1_PWM=-40;
    let MTR2_PWM=40;
  
    let  payload_data  = new Uint8Array(2);
    payload_data[0] = MTR1_PWM;
    payload_data[1] = MTR2_PWM;
    
    
    let  payload_cmd  = new Uint8Array(2);
    payload_cmd [0] = 0x05;//controls both motors
    payload_cmd [1] = 0x00;
    
    movecmd(payload_cmd,payload_data);

    console.log("Turn Right")
}

function stop() {
  //both PWM's set to 0 to stop.
  let MTR1_PWM=0;
  let MTR2_PWM=0;
  

  let  payload_data  = new Uint8Array(2);
  payload_data[0] = MTR1_PWM;
  payload_data[1] = MTR2_PWM;
  
  
  let  payload_cmd  = new Uint8Array(2);
  payload_cmd [0] = 0x05;//controls both motors
  payload_cmd [1] = 0x00;
 
  movecmd(payload_cmd,payload_data);
  console.log("Stop")

}

man-in-the-loop exercise

To make this experiment go a little smoother for you, we will adapt all speed constants for each button with same PWM strength. See below where we set these values to 40 respectively. Conducting this experiment four times is a good starting point for this exercise: So in order to do so, let's conduct the experiment as follows:

  • Step 1: Save the HTML (mthjoystick.html) and JavaScript (mthjoystick.js) files to your local machine.

  • Step 2: Modify the MTR1_PWM and MTR2_PWM values in each function below and save mthjoystick.js after each edit in your text editor.

  • Step 3: Open the HTML file in Google Chrome and bring up the GUI in your browser and connect your sensor. You can refresh the page it is already opened from a previous experiment.

  • Step 3: Maneuver the complete obstacle course and note your results.

  • Step 4: Record notes regarding, how many times you hit obstacles, how easy was it to keep the Rover on track etc.

  • Repeat steps 2 through 4, while increasing the MTRX_PWM value by 25.

  • This exercise should be repeated with final obstacle course loop being conducted with PWM strengths being set to 100 each respectively.

function moveBackward() {
  
     //both PWM's set to same direction.
     let MTR1_PWM=-40;
     let MTR2_PWM=-40;
     
 
     let  payload_data  = new Uint8Array(2);
     payload_data[0] = MTR1_PWM;
     payload_data[1] = MTR2_PWM;
     
     
     let  payload_cmd  = new Uint8Array(2);
     payload_cmd [0] = 0x05;//controls both motors
     payload_cmd [1] = 0x00;
     
     movecmd(payload_cmd,payload_data);
 
     console.log("Move Backward")
 
 }

function moveForward() {
    //both PWM's set to to the same negative direciton.
    let MTR1_PWM=40;
    let MTR2_PWM=40;
    

    let  payload_data  = new Uint8Array(2);
    payload_data[0] = MTR1_PWM;
    payload_data[1] = MTR2_PWM;
    
    
    let  payload_cmd  = new Uint8Array(2);
    payload_cmd [0] = 0x05;//controls both motors
    payload_cmd [1] = 0x00;
    
    movecmd(payload_cmd,payload_data);

    console.log("Move Forward")

}

function turnLeft() {
   //both PWM's set to opposited directions.
   let MTR1_PWM=40;
   let MTR2_PWM=-40;
   

   let  payload_data  = new Uint8Array(2);
   payload_data[0] = MTR1_PWM;
   payload_data[1] = MTR2_PWM;
   
   
   let  payload_cmd  = new Uint8Array(2);
   payload_cmd [0] = 0x05;//controls both motors
   payload_cmd [1] = 0x00;
   
   movecmd(payload_cmd,payload_data);

   console.log("Turn Left")

}

function turnRight() {
    
   //both PWM's set to opposite directions.
    let MTR1_PWM=-40;
    let MTR2_PWM=40;
    

    let  payload_data  = new Uint8Array(2);
    payload_data[0] = MTR1_PWM;
    payload_data[1] = MTR2_PWM;
    
    
    let  payload_cmd  = new Uint8Array(2);
    payload_cmd [0] = 0x05;//controls both motors
    payload_cmd [1] = 0x00;
    
    movecmd(payload_cmd,payload_data);

    console.log("Turn Right")
}

function stop() {
  //both PWM's set to 0 to stop.
  let MTR1_PWM=0;
  let MTR2_PWM=0;
  

  let  payload_data  = new Uint8Array(2);
  payload_data[0] = MTR1_PWM;
  payload_data[1] = MTR2_PWM;
  
  
  let  payload_cmd  = new Uint8Array(2);
  payload_cmd [0] = 0x05;//controls both motors
  payload_cmd [1] = 0x00;
 
  movecmd(payload_cmd,payload_data);
  console.log("Stop")
🚗
https://mantiscode.azurewebsites.net/webble/mthjoystick.html