Skip to content

Code Base

Launch Files

Omnibot

'launch/omnibot.launch'

Starts up nodes for production. Robot listens for velocity updates.

1
2
3
4
5
6
<launch>
   <!-- Start Ultrasound Node -->
  <node name="ultrasoundPublisher_node" pkg="omnibot" type="ultrasoundPublisher_node.py" />
   <!-- Start ROSserial node -->
  <node name="arduino_node" pkg="omnibot" type="start_rosserial.sh" />
</launch>

Omnibot Dev

'launch/omnibot_dev.launch'

Starts up nodes for development and troubleshooting. Robot listens to incoming velocities from velocityPublisher node.

1
2
3
4
5
6
7
8
<launch>
  <!--  Start velocityPublisher Node-->
  <node name="velocityPublisher_node" pkg="omnibot" type="velocityPublisher_node.py" />
  <!--  Start Ultrasound Node -->
  <node name="ultrasoundPublisher_node" pkg="omnibot" type="ultrasoundPublisher_node.py" />
  <!--  Start ROSserial Node-->
  <node name="arduino_node" pkg="omnibot" type="start_rosserial.sh" />
</launch>

Script Files

Ultrasound Node

'scripts/ultrasoundPublisher_node.py'

Publishes an integer value representing distance to target in millimeters

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#!/usr/bin/python
from time import time
from serial import Serial # import library allowing python to interact with serial port
import rospy
from std_msgs.msg import Int8 # distance acquired will be fit into an 8bit variable

serialDevice = "/dev/ttyAMA0" # default for RaspberryPi
maxwait = 5 # seconds to try for a good reading before quitting

# Function was obtained from the Maxbotix website.
# https://www.maxbotix.com/wp-content/uploads/2017/09/074_raspPi.txt
def get_measurement(portName):
    ser = Serial(portName, 9600, 8, 'N', 1, timeout=1)
    timeStart = time()
    valueCount = 0

    while time() < timeStart + maxwait:
        if ser.inWaiting():
            bytesToRead = ser.inWaiting()
            valueCount += 1
            if valueCount < 2: # 1st reading may be partial number; throw it out
                continue
            testData = ser.read(bytesToRead)
            if not testData.startswith(b'R'):
                # data received did not start with R
                continue
            try:
                sensorData = testData.decode('utf-8').lstrip('R')
            except UnicodeDecodeError:
                # data received could not be decoded properly
                continue
            try:
                mm = int(sensorData)
            except ValueError:
                # value is not a number
                continue
            ser.close()
            return(mm)

    ser.close()
    raise RuntimeError("Expected serial data not received")

def publish_measurement(portName):
    # Declare publisher
    # Transmitting to => ultrasoundDistance_topic
    # Using the 'Int8' message type
    # A maximnum of 10 messages will be held in the message queue.
    pub = rospy.Publisher('ultrasoundDistance_topic', Int8, queue_size=10)
    rospy.init_node('ultrasoundPublisher_node')
    # 10 readings will be published per second
    rate = rospy.Rate(10) # 10hz

    # This keeps the node running until the script is shut down manually.
    # node will keep cycling at the frequncy set above.
    while not rospy.is_shutdown():
        # obtain measurement
        measurement = get_measurement(portName)

        # Log measurment to the terminal.
        rospy.loginfo(measurement)
        # publish measurmeent to the 'ultrasoundDistance_topic'.
        pub.publish(measurement)
        # Causes ROS to pause, to ensure its cycleing at the frequency set above.
        rate.sleep()

if __name__ == '__main__':
    publish_measurement(serialDevice)

ROSserial (Arduino)

Start ROSserial Node

'scripts/start_rosserial.bash'

Starts up rosserial node (arduino). Run only after arduino is physically connected.

1
2
#!/bin/bash
rosrun rosserial_python serial_node.py _port:=/dev/ttyACM0

Motors Only (No ROS)

'scripts/arduino/basicMotor_noROS.ino'

Starts up three step motors at 60 rpms each. ROS is not needed, as script starts up automatically with power source.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <Wire.h>
#include <Adafruit_MotorShield.h> // library nbeeded to interact with motorshields

// Declare motorshield objects at their locations
Adafruit_MotorShield AFMStop(0x61);
Adafruit_MotorShield AFMSbot(0x60);

// Get motor handler objects from motorshield objects
// Documentation on motors states 200 steps per rev
// AFMStop.getStepper(200, 2) => AFMStop.getStepper(#OfSteps, motorShieldPort#)
Adafruit_StepperMotor *stepMotor_1 = AFMStop.getStepper(200, 2);
Adafruit_StepperMotor *stepMotor_2 = AFMSbot.getStepper(200, 1);
Adafruit_StepperMotor *stepMotor_3 = AFMSbot.getStepper(200, 2);

// NOTE velocities are in rpm!!!!
float stepMotor1_vel= 60;
float stepMotor2_vel= 60;
float stepMotor3_vel= 60;

void setup()
{  
  AFMSbot.begin(); // Initialize the bottom shield
  AFMStop.begin(); // Initialize the top shield

  stepMotor_1->setSpeed(stepMotor1_vel); // top 
  stepMotor_2->setSpeed(stepMotor2_vel); // bot
  stepMotor_3->setSpeed(stepMotor3_vel); // bot
}

void loop()
{
  // NOTE => step(#OfSteps, direction, stepType)

  // clockwise
  stepMotor_1->step(1, FORWARD, DOUBLE);
  // counter-clockwise
  stepMotor_2->step(1, BACKWARD, DOUBLE);
  // clockwise
  stepMotor_3->step(1, FORWARD, DOUBLE);
}

Motor Control Flow (w/ROS)

Below is a simplified version of teh arduino control loop and ROS cycle, when controlling the step motors. alt Motor Control Flow

Motors Only (w/ ROS)

'scripts/arduino/basicMotor_wROS.ino'

Starts up three step motors at 0 rpms each. ROS is used to subscribe for velocity updates. Velocities are not actuaized until a power source is connected. Features 2 publsihers that transmit angular displacements and velocities of each motor.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
#include <Wire.h>
#include <Adafruit_MotorShield.h> // library nbeeded to interact with motorshields
#include <ros.h> // library needed for ROS communication
#include <omnibot/MotorArray.h> // library needed for custom ROS 'MotorArray' msg type

ros::NodeHandle nh; // Instantiate ros handler object

// Declare motorshield objects
// This allows us to then interact with motors attached to our
// motorshields.
Adafruit_MotorShield AFMStop(0x61);
Adafruit_MotorShield AFMSbot(0x60);

// Initialize motor handler objects from the ports on the motorshields.
// AFMStop.getStepper(200, 2) => motorShieldObject.getSTepper
Adafruit_StepperMotor *stepMotor_1 = AFMStop.getStepper(200, 2);
Adafruit_StepperMotor *stepMotor_2 = AFMSbot.getStepper(200, 1);
Adafruit_StepperMotor *stepMotor_3 = AFMSbot.getStepper(200, 2);

// Declare array of step motors
// Array is declared to 4 members, but we only index motors from 1-3
// This was done for clarity of new users.
Adafruit_StepperMotor* motorArray[4];

// Initialize motors at angular_vel=0; available globally
// NOTE => Velocities are in RPMs !!
float velocityArray[4] = {0}; 

// Step motors will vibrate regardless of what velocity is sent to the motor.
// If motor velocity is '0', the motor will still vibrate.
// To avoid this, we define a thershold. if the desired velocity is below 
// this threshold, no step will be issued to the motor.
// The motor velocity variables will still reflect this desired velosity, 
// even if it crosses the threshold.
float velocityThreshold = 0.1;

// Declare custom made variables that will hold our velocities and displacements,
// that will be published to their respective topics.
omnibot::MotorArray currentVelocities;
omnibot::MotorArray angularDisplacements; // NOTE DISPLACEMNTS ARE IN RADIANS!!

// Callback function for 'setStepMotorVelocity_topic' subscriber below. 
// Updates velocity variables from FIFO queue, but does not send a step 
// command to the motors.
// The velocityArray is globally available. After this function updates 
// its values, the array is available for any other function to use.
// The desired values are assign to the velocityArray, rgardless of 
// whether they are wihtin the threshold or not.
void updateMotorVelocities( const omnibot::MotorArray& velocity_msg)
{
  // assign velocityArray values as recieved from 'setStepMotorVelocity_topic' 
  velocityArray[1] = velocity_msg.motor1; velocityArray[2] = velocity_msg.motor2; velocityArray[3] = velocity_msg.motor3;

  // The absolute value of the velocityArray values is sent to the motors.
  // Negative values sent to the motors cause incorrect rotation.
  // To actualize direction (CW and CCW), we use the "BACKWARD"/"FORWARD" parameters in the step command.
  motorArray[1]->setSpeed(abs(velocityArray[1]));
  motorArray[2]->setSpeed(abs(velocityArray[2]));
  motorArray[3]->setSpeed(abs(velocityArray[3]));
}

// Decides whether a desired velocity (absolute value of desired velocity) 
// is above (TRUE) the threshold, or below (FALSE).
bool velocityIsAboveThreshold(float motorVel, float threshold)
{
  if ( abs(motorVel) > threshold ) {
    return true;
  }
  return false;
}

// Grabs the values of the desired velocities, but takes the threshold 
// value nto consideration. If the value is above the threshold, 
// the velocity is returned. if value is below th ethreshold, a '0' is returned.
float getCurrentMotorVelocity(float velocity, float threshold)
{ 
  if (velocityIsAboveThreshold(velocity, threshold)) {
    return velocity;
  }
  return 0;
}

// Calculates the angular displacement, depending on the direction of rotation.
// Our step motors are 200step motors. (Take 200 steps to rotate 360 degrees).
// With each step being 1.8degrees and our velocities being relatively slow, w ecan
// estimate our angular displacement per step as -1.8 || +1.8 dgerees.
// Positive angular velocity (CCW) +1.8 == +0.0314159
// Negative angular velocity (CW) -1.8 == -0.0314159
// If velocity is zero (ie scenarios where vleocity is below threshold), a '0' is
// returned for no angular displacement.
float getAngularDisplacement(float velocity)
{
  if (velocity > 0) {
    return 0.0314159;
  }
  else if(velocity < 0) {
    return -0.0314159;
  }
  else {
    return 0;
  } 
}

// Sends step command to the motors.
// Takes threhsold into consideration. If desired velocity is '0' (ie like when below threshold),
// then no step is issued. No need to send a step command if the velocity is zero.
// This takes positive/negative signs into consideration. If desired velocity is
// Positive angular velocity (CCW) == BACKWARD
// Negative angular velocity (CW) == FORWARD
// Step command is below:
// motorArray[motorNumber]->step(#OfSteps, DIRECTION, stepTYPE)
void motorStep(float motorVel, int motorNumber)
{
  if (velocityIsAboveThreshold(motorVel, velocityThreshold)) {
    if(motorVel < 0) {
      motorArray[motorNumber]->step(1, BACKWARD, DOUBLE); // clockwise
    }
    else {
      motorArray[motorNumber]->step(1, FORWARD, DOUBLE); // counter-clockwise
    }
  }
}

// Initializes the publishers that report our current velocities and 
// angular displacmeents to the Pi.
ros::Publisher currentMotorVelocities_topic("currentMotorVelocities_topic", &currentVelocities);
ros::Publisher angularDisplacements_topic("angularDisplacements_topic", &angularDisplacements);

// Assigns the velocities from our currently desired values to our
// custom motorArray msg object.
void publishCurrentMotorVelocities()
{
  currentVelocities.motor1 = getCurrentMotorVelocity(velocityArray[1], velocityThreshold);
  currentVelocities.motor2 = getCurrentMotorVelocity(velocityArray[2], velocityThreshold);
  currentVelocities.motor3 = getCurrentMotorVelocity(velocityArray[3], velocityThreshold);

  currentMotorVelocities_topic.publish( &currentVelocities );
}

// Assigns the angular displacement (WRT our current iteration's velocity) toour
// custom motorArray msg object.
void publishAngularDisplacements()
{ 
  angularDisplacements.motor1 = getAngularDisplacement(currentVelocities.motor1);
  angularDisplacements.motor2 = getAngularDisplacement(currentVelocities.motor2);
  angularDisplacements.motor3 = getAngularDisplacement(currentVelocities.motor3);

  angularDisplacements_topic.publish( &angularDisplacements );
}

// Set up motorVelocities subscriber
// The velocity for each stepMotor is declared inside of a custom motorArray ROS msg
// and is recioeved through the 'setStepMotorVelocity_topic'.
ros::Subscriber<omnibot::MotorArray> motorVelocities("setStepMotorVelocity_topic", &updateMotorVelocities );

// Iterates through the stepMotor array and performs the step command (if applicable).
void activateMotors()
{
  for(int i = 1; i < 4; i++)
  {
    motorStep(velocityArray[i], i);
  }
}

void setup()
{
  Serial.begin(115200); // set baud rate
  nh.initNode(); // Initialize ROSserial node

  // notify master of our new publishers and subscribers
  nh.subscribe(motorVelocities);
  nh.advertise(currentMotorVelocities_topic);
  nh.advertise(angularDisplacements_topic);

  AFMSbot.begin(); // Initialize the bottom shield
  AFMStop.begin(); // Initialize the top shield

  // add motor instances to our motorArray
  // ommited index=0 for clarity
  motorArray[1] = stepMotor_1;
  motorArray[2] = stepMotor_2;
  motorArray[3] = stepMotor_3;
}

void loop()
{
  // This causes ROS to cycle.
  // Normally (on a multithreaded device) each subscriber
  // works in its own thread. Because this device is 
  // single threaded, this function aids in protothreading and making
  // sure our values are updated accordingly.
  nh.spinOnce();

  // Processes current desired velocities and sends the 
  activateMotors();

  // step commands ot the motors// Publishes currrent motor velocities to 'currentMotorVelocities_topic'.
  publishCurrentMotorVelocities();  
  // Publishes the angular displacement for the current iteration to 'angularDisplacements_topic'.
  publishAngularDisplacements();
}

Sensors Only (w/ ROS)

'scripts/arduino/basicSensors_wROS.ino'

Begins reading data from arudino's onboard accelerometer and gyroscope. ROS is used to publish linear acceleration, orientation, and angular velocity. External power source not needed (only USB). No subscribers.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
#include <ros.h> // ROS library
#include <CurieIMU.h> // accelerometer library
#include <MadgwickAHRS.h> // noise filter library
#include <geometry_msgs/Vector3.h> // ROS messages library

// Instantiate ROS handler object.
// (this makes it a ROS node)
ros::NodeHandle nh; 

// preallocate Vector3 variables to hold:
// --------------------------------------
// var orientation = orientation 
// var linearAccel = linear acceleration
// var angularVel = angular velocity
geometry_msgs::Vector3 orientation; 
geometry_msgs::Vector3 linearAccel;
geometry_msgs::Vector3 angularVel;

// Set up publishers for each of the variables above.
// Each of these publishers will publish each of the variables above,
// every time the arduino loop executes (activated by the "spinOnce" on line 141).
ros::Publisher orientation_topic("orientation_topic", &orientation);
ros::Publisher linearAccel_topic("linearAccel_topic", &linearAccel);
ros::Publisher angularVel_topic("angularVel_topic", &angularVel);

// Create a Madgwick object to access the functions from the 
// Madgwick class in the library. Here, we call it filter.
// A prewritten class from ADAFRUIT website that will allow 
// us to get a reading from the onboard gyroscope and accelerometer.
// https://www.arduino.cc/en/Tutorial/Genuino101CurieIMUOrientationVisualiser
Madgwick filter;

// initialize sensor data variables
unsigned long microsPerReading, microsPrevious;
float accelScale, gyroScale;

// The algorithm takes raw values from a gyroscope and accelerometer, 
// and uses them to return four quaternions:

int aix, aiy, aiz;
int gix, giy, giz;

float ax, ay, az;
float gx, gy, gz;

// which are 4-dimensional numbers which contain x, y, and z values to 
// represent the axis around which rotation occurs, as well as a ω value
// which represents the value of rotation which occurs around the same 
// axis. These quaternions can be used to calculate the Euler angles 
// pitch, yaw, and roll.

float roll, pitch, heading;
unsigned long microsNow;

// Helper functions for translating quarternion readings.
// Both are used in 'publishLinearAccelAndangularVel()'
float convertRawAcceleration(int aRaw) {
  // since we are using 2G range
  // -2g maps to a raw value of -32768
  // +2g maps to a raw value of 32767

  float a = (aRaw * 2.0) / 32768.0;
  return a;
}
float convertRawGyro(int gRaw) {
  // since we are using 250 degrees/seconds range
  // -250 maps to a raw value of -32768
  // +250 maps to a raw value of 32767

  float g = (gRaw * 250.0) / 32768.0;
  return g;
}

// This function takes care of three different tasks
// 1) Translatemrat raw quarternion readings to:
//    -Gs for linear accelration (line )
//    -Degrees/sec for angular velocity
// 2) Publish tranlated motion values to their respective topics
// 3) Update the the Madgwick object (the filter) to later obtain 
//    our roll, pitch, and yaw
void publishLinearAccelAndangularVel()
{
  // convert from raw data to gravity and degrees/second units
  ax = convertRawAcceleration(aix);
  ay = convertRawAcceleration(aiy);
  az = convertRawAcceleration(aiz);
  gx = convertRawGyro(gix);
  gy = convertRawGyro(giy);
  gz = convertRawGyro(giz);

  // update the filter, which computes orientation
  // we will call this filter in a separata function to get roll, pitch, yaw
  filter.updateIMU(gx, gy, gz, ax, ay, az);

  // attach linear accel and rotational vel values for publishing
  linearAccel.x = ax;
  linearAccel.y = ay;
  linearAccel.z = az;
  angularVel.x = gx;
  angularVel.y = gy;
  angularVel.z = gz;

  // publish values
  linearAccel_topic.publish( &linearAccel );
  angularVel_topic.publish( &angularVel );
}

// Retrieves roll, pitch, and yaw from the filter (Madgqiwck object)
// and publishes it to the orientation topic.
// Roll, pitch, and yaw are calculated according to the update performed on the filter 
// from 'publishLinearAccelAndangularVel()' function (runs beforehand).
void publishOrientation()
{
  roll = filter.getRoll();
  pitch = filter.getPitch();
  heading = filter.getYaw();

  orientation.x = roll;
  orientation.y = pitch;
  orientation.z = heading;

  orientation_topic.publish( &orientation );
}

// Checks to ensure its time to take a new reading, according to timestep defined
// inside of the 'setup()' function.
// If its time for a reading, linearAcceleration, angularVelocity,a nd orientation are
// all read and published.
void processAndPublishSensorData()
{
  // Timestep is calculated below.
  // Configuration for the itme step is set in the 'setup()' function.
  microsNow = micros();
  if (microsNow - microsPrevious >= microsPerReading) {

    // read raw data from CurieIMU and store in 'aix, aiy, aiz, gix, giy, giz'
    // These variables are available globally!
    CurieIMU.readMotionSensor(aix, aiy, aiz, gix, giy, giz);

    publishLinearAccelAndangularVel();
    publishOrientation();

    // increment previous time, so we keep proper pace
    microsPrevious = microsPrevious + microsPerReading;
  }
}

void setup()
{
  // Set baud rate for serial transmission.
  Serial.begin(115200); 
  // Initilizes the ROS node.
  nh.initNode();  

  // Notify the master node that we have 3 publishers declared above.
  nh.advertise(orientation_topic);
  nh.advertise(linearAccel_topic);
  nh.advertise(angularVel_topic);

  // Start the IMU and perform preliminary configuration by 
  // setting the sample rate of the acelerometer and the gyro 
  // and the filter to 25Hz:
  CurieIMU.begin();
  CurieIMU.setGyroRate(25); //25 Hz
  CurieIMU.setAccelerometerRate(25); // 25Hz
  filter.begin(25); // 25Hz

  // Set the accelerometer range to 2G
  CurieIMU.setAccelerometerRange(2);

  // Set the gyroscope range to 250 degrees/second
  CurieIMU.setGyroRange(250);

  // initialize variables to pace updates to correct rate
  microsPerReading = 1000000 / 25;
  microsPrevious = micros();
}

void loop()
{
  // This causes ROS to cycle.
  // Normally (on a multithreaded device) each subscriber
  // works in its own thread. Because this device is 
  // single threaded, this function aids in protothreading and making
  // sure our values are updated accordingly.
  nh.spinOnce();

  // This line processes all other functions, preparing and publishing 
  // all outgoing data.
  processAndPublishSensorData();
}

Motors + Sensors (w/ ROS)

'scripts/arduino/motorsAndSensors_wROS.ino'

Starts up three motors and onboard accelerometer/gyroscope. Subscribes to velocity updates. Publishes angular dispacmeents, motor velocities, orientation, linear acceleration, and angular velocity.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
// =================================================
// Motor Libraries [START] -------------------------
#include <Wire.h>
#include <Adafruit_MotorShield.h>
#include <ros.h>
#include <std_msgs/Int64.h>
#include <std_msgs/Float32MultiArray.h>
#include <geometry_msgs/Point.h>
#include <omnibot/MotorArray.h>
// Motor Libraries [END] ---------------------------
// =================================================
// =================================================
// Sensor Libraries [START] ------------------------
#include <CurieIMU.h>
#include <MadgwickAHRS.h>
#include <geometry_msgs/Vector3.h>
// Sensor Libraries [END] --------------------------
// =================================================

ros::NodeHandle nh;

// ==============================================================
// Motor variable declaration [START]----------------------------
Adafruit_MotorShield AFMStop(0x61);
Adafruit_MotorShield AFMSbot(0x60);

Adafruit_StepperMotor *stepMotor_1 = AFMStop.getStepper(200, 2);
Adafruit_StepperMotor *stepMotor_2 = AFMSbot.getStepper(200, 1);
Adafruit_StepperMotor *stepMotor_3 = AFMSbot.getStepper(200, 2);

Adafruit_StepperMotor* motorArray[4];
float velocityArray[4] = {0}; // initialize motors at angular_vel=0; available globally
float velocityThreshold = 0.1;

omnibot::MotorArray currentVelocities;
omnibot::MotorArray angularDisplacements;

ros::Publisher currentMotorVelocities_topic("currentMotorVelocities_topic", &currentVelocities);
ros::Publisher angularDisplacements_topic("angularDisplacements_topic", &angularDisplacements);
// Motor variable declaration [END]------------------------------
// ==============================================================

// ==============================================================
// Sensor variable declaration [START]----------------------------
geometry_msgs::Vector3 orientation;
geometry_msgs::Vector3 linearAccel;
geometry_msgs::Vector3 angularVel;

ros::Publisher linearAccel_topic("linearAccel_topic", &linearAccel);
ros::Publisher angularVel_topic("angularVel_topic", &angularVel);
ros::Publisher orientation_topic("orientation_topic", &orientation);

Madgwick filter;

unsigned long microsPerReading, microsPrevious;
float accelScale, gyroScale;

int aix, aiy, aiz;
int gix, giy, giz;

float ax, ay, az;
float gx, gy, gz;

float roll, pitch, heading;
unsigned long microsNow;
// Sensor variable declaration [END]------------------------------
// ==============================================================

// ==============================================================
// Motor function and callbacks [START]--------------------------
// callback function for subscriber below. Updates velocities from FIFO queue
void updateMotorVelocities( const omnibot::MotorArray& velocity_msg)
{
  velocityArray[1] = velocity_msg.motor1; velocityArray[2] = velocity_msg.motor2; velocityArray[3] = velocity_msg.motor3;

  motorArray[1]->setSpeed(abs(velocityArray[1]));
  motorArray[2]->setSpeed(abs(velocityArray[2]));
  motorArray[3]->setSpeed(abs(velocityArray[3]));
}

bool velocityIsAboveThreshold(float motorVel, float threshold)
{
  if ( abs(motorVel) > threshold ) {
    return true;
  }
  return false;
}

float getCurrentMotorVelocity(float velocity, float threshold)
{

  if (velocityIsAboveThreshold(velocity, threshold)) {
    return velocity;
  }
  return 0;
}

float getAngularDisplacement(float velocity)
{
  if (velocity > 0) {
    return 0.0314159;
  }
  else if(velocity < 0) {
    return -0.0314159;
  }
  else {
    return 0;
  } 
}

void motorStep(float motorVel, int motorNumber)
{
  if (velocityIsAboveThreshold(motorVel, velocityThreshold)) {
    if(motorVel < 0) {
      motorArray[motorNumber]->step(1, BACKWARD, DOUBLE); // clockwise
    }
    else {
      motorArray[motorNumber]->step(1, FORWARD, DOUBLE); // counter-clockwise
    }
  }
}

void publishCurrentMotorVelocities()
{
  currentVelocities.motor1 = getCurrentMotorVelocity(velocityArray[1], velocityThreshold);
  currentVelocities.motor2 = getCurrentMotorVelocity(velocityArray[2], velocityThreshold);
  currentVelocities.motor3 = getCurrentMotorVelocity(velocityArray[3], velocityThreshold);

  currentMotorVelocities_topic.publish( &currentVelocities );
}

void publishAngularDisplacements()
{ 
  angularDisplacements.motor1 = getAngularDisplacement(currentVelocities.motor1);
  angularDisplacements.motor2 = getAngularDisplacement(currentVelocities.motor2);
  angularDisplacements.motor3 = getAngularDisplacement(currentVelocities.motor3);

  angularDisplacements_topic.publish( &angularDisplacements );
}

// Set up motorVelocities subscriber
ros::Subscriber<omnibot::MotorArray> motorVelocities("setStepMotorVelocity_topic", &updateMotorVelocities );

void activateMotors()
{
  for(int i = 1; i < 4; i++)
  {
    motorStep(velocityArray[i], i);
  }
}
// Motor function and callbacks [END]----------------------------
// ==============================================================

// ==============================================================
// Sensor function and callbacks [START]--------------------------
float convertRawAcceleration(int aRaw) {
  // since we are using 2G range
  // -2g maps to a raw value of -32768
  // +2g maps to a raw value of 32767

  float a = (aRaw * 2.0) / 32768.0;
  return a;
}

float convertRawGyro(int gRaw) {
  // since we are using 250 degrees/seconds range
  // -250 maps to a raw value of -32768
  // +250 maps to a raw value of 32767

  float g = (gRaw * 250.0) / 32768.0;
  return g;
}

void publishLinearAndangularVel()
{
  ax = convertRawAcceleration(aix);
  ay = convertRawAcceleration(aiy);
  az = convertRawAcceleration(aiz);

  gx = convertRawGyro(gix);
  gy = convertRawGyro(giy);
  gz = convertRawGyro(giz);

  filter.updateIMU(gx, gy, gz, ax, ay, az);

  linearAccel.x = ax;
  linearAccel.y = ay;
  linearAccel.z = az;

  angularVel.x = gx;
  angularVel.y = gy;
  angularVel.z = gz;

  linearAccel_topic.publish( &linearAccel );
  angularVel_topic.publish( &angularVel );
}

void publishOrientation()
{
  roll = filter.getRoll();
  pitch = filter.getPitch();
  heading = filter.getYaw();

  orientation.x = roll;
  orientation.y = pitch;
  orientation.z = heading;

  orientation_topic.publish( &orientation );
}

void processAndPublishSensorData()
{
  microsNow = micros();
  if (microsNow - microsPrevious >= microsPerReading) {

    // read raw data from CurieIMU
    CurieIMU.readMotionSensor(aix, aiy, aiz, gix, giy, giz);

    publishLinearAndangularVel();
    publishOrientation();

    // increment previous time, so we keep proper pace
    microsPrevious = microsPrevious + microsPerReading;
  }
}
// Sensor function and callbacks [END]----------------------------
// ==============================================================

void setup()
{
  Serial.begin(115200); // set baud rate
  nh.initNode();

  // =============================================
  // Motor setup [START]--------------------------
  nh.subscribe(motorVelocities);
  nh.advertise(currentMotorVelocities_topic);
  nh.advertise(angularDisplacements_topic);

  AFMSbot.begin();
  AFMStop.begin();

  motorArray[1] = stepMotor_1;
  motorArray[2] = stepMotor_2;
  motorArray[3] = stepMotor_3;
  // Motor setup [END]--------------------------
  // ===========================================

  // =============================================
  // Sensor setup [START]--------------------------
  nh.advertise(orientation_topic);
  nh.advertise(linearAccel_topic);
  nh.advertise(angularVel_topic);

  CurieIMU.begin();
  CurieIMU.setGyroRate(25);
  CurieIMU.setAccelerometerRate(25);
  filter.begin(25);

  CurieIMU.setAccelerometerRange(2);
  CurieIMU.setGyroRange(250);

  microsPerReading = 1000000 / 25;
  microsPrevious = micros();
  // Sensor setup [END]--------------------------
  // ===========================================
}

void loop()
{
  nh.spinOnce();
  // ====================================================
  // Motor loop [START]----------------------------------
  activateMotors();
  publishCurrentMotorVelocities();
  publishAngularDisplacements();
  // Motor loop [END]----------------------------------
  // ====================================================
  // ====================================================
  // Sensor loop [START]----------------------------------
  processAndPublishSensorData();
  // Sensor loop [END]----------------------------------
  // ====================================================
}

Script Files (Dev Only)

Velocity Publisher Node

'scripts/velocityPublisher_node.py'

Sets up motor velocities at 100 rpms each. Begins decreasing velocity at 1rpm per cycle (1 second) Motors will chnage direction, when velocities become negative.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#!/usr/bin/env python
import rospy #import ROS pip package for using ROS library
from omnibot.msg import MotorArray # import our custom ROS msg types

def VelocityPublisher_talker():
    # Declare publisher
    # Transmitting to => setStepMotorVelocity_topic
    # Using the 'MotorArray' message type
    # A maximnum of 10 messages will be held in the message queue.
    pub = rospy.Publisher('setStepMotorVelocity_topic', MotorArray, queue_size=10)

    # Initialize node
    rospy.init_node('velocityPublisher_node')

    # Sets ROS to cycle at frequency of 1Hz
    rate = rospy.Rate(1) 

    # Set uinitial velocity to 100 rpms
    velocity=100.00

    # This keeps the node running until the script is shut down manually.
    # node will keep cycling at the frequncy set above.
    while not rospy.is_shutdown(): 
        # Velocity decreases by 1 rpm every cycle
        velocity -= 1.0
        # Logs our MotorArray to the terminal
        rospy.loginfo([velocity,velocity,velocity])
        # Publishes our motorArray to the 'setStepMotorVelocity_topic'
        pub.publish(velocity,velocity,velocity)
        # Causes the while loop to pause until its time to cycle again.
        # How long it sleeps (pauses) depends on our declared frequnecy above.
        rate.sleep()

if __name__ == '__main__':
    try:
        VelocityPublisher_talker()
    except rospy.ROSInterruptException:
        pass

Velocity Publisher Node 1

'scripts/velocityPublisher_node_1.py'

Starts the motors at 30 rpm. Causes the motors to alternate directions every 5 seconds.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/env python
import time
import rospy
from omnibot.msg import MotorArray

# Starts the motors at 30 rpm.
# Causes the motors to alternate directions every 5 seconds.

def VelocityPublisher_talker():
    pub = rospy.Publisher('setStepMotorVelocity_topic', MotorArray, queue_size=10)
    rospy.init_node('velocityPublisher_node')
    rate = rospy.Rate(1)
    velocity = 30.00
    while not rospy.is_shutdown():
        velocity = velocity * (-1) 
        pub.publish(velocity,velocity,velocity)
        time.sleep(5)

if __name__ == '__main__':
    try:
        VelocityPublisher_talker()
    except rospy.ROSInterruptException:
        pass

Velocity Publisher Node 2

'scripts/velocityPublisher_node_2.py'

Displays both directions for each motor in the following order. Velocities in either direction ar set to 30 rpm. Scenarios alternate every 5 seconds.

Set -> 1 Motor1 = CCW; Motor2 = CCW; Motor3= CCW

Set -> 2 Motor1 = 0; Motor2 = 0; Motor3= 0

Set -> 3 Motor1 = CW; Motor2 = CW; Motor3= CW

Set -> 4 Motor1 = CCW; Motor2 = 0; Motor3= CW

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#!/usr/bin/env python
import time
import rospy
from omnibot.msg import MotorArray

# Displays both directions for each motor in the following order.
# Velocities in either direction ar set to 30 rpm.
# Scenarios alternate every 5 seconds.

# Set -> 1
# Motor1 = CCW; Motor2 = CCW; Motor3= CCW

# Set -> 2
# Motor1 = 0; Motor2 = 0; Motor3= 0

# Set -> 3
# Motor1 = CW; Motor2 = CW; Motor3= CW

# Set -> 4
# Motor1 = CCW; Motor2 = 0; Motor3= CW

def VelocityPublisher_talker():
    pub = rospy.Publisher('setStepMotorVelocity_topic', MotorArray, queue_size=10)
    rospy.init_node('velocityPublisher_node')
    rate = rospy.Rate(1) # 10hz
    pos_velocity = 30.00
    no_velocity = 0
    neg_velocity = -30.00
    while not rospy.is_shutdown():
        time.sleep(5)
        pub.publish(pos_velocity,pos_velocity,pos_velocity)
        time.sleep(5)
        pub.publish(no_velocity,no_velocity,no_velocity)
        time.sleep(5)
        pub.publish(neg_velocity,neg_velocity,neg_velocity)
        time.sleep(5)
        pub.publish(pos_velocity,no_velocity,neg_velocity)

if __name__ == '__main__':
    try:
        VelocityPublisher_talker()
    except rospy.ROSInterruptException:
        pass