MCP
MCP robot

Steering Subsystem

Contents:

1. Introduction

In this paper we will discuss the various parts of the steering subsystem on the MCP. The first chapter will cover the hardware that is used in this vehicle. It will go in depth about the circuit boards that are used and the physical modifications that we made. The second chapter will discuss the intricacies of the code such as the communication methods between both the master control program and the other circuit boards. It will also discuss the choices that are made to make our steering sub system work. The third chapter will show the code that was discussed in the second chapter. Finally the last chapter will discuss what still needs to be done to this sub system to make it fully functional.

2. Hardware

Design History 

When we started off on this project we were presented with a vehicle which was using a van door motor to steer the front wheels. The motor was connected to a gear, which drove a much larger gear that was connected to the steering shaft. The steering shaft hooks into a metal bar, which runs perpendicular to the wheels, and moves it left and right to steer the wheels.

Our first decision was to change the way the motor hooked up to the steering shaft, because the gears were not meshed properly and they would often skip. When we assessed the problem, we decided that simply repositioning the gears would not be easy or effective. We decided that we should directly connect the motor to the steering shaft. We connected the motor to the steering shaft using a rubber tubing which was reinforced with several layers of duct tape. We clamped each side of the tubing down to the shaft which it was covering. This idea never fully worked because the motor was so strong that it twisted the tubing and eventually tore right through it. Our next iteration was to use the same principle but instead of rubber tubing we used two layers of plastic tubing, each of different thickness and durability. This worked for only a few test runs because eventually the motor shaft had chafed away at the inner tube to the point where it would spin freely inside of it. At this point we decided it was time to make a custom metal part which could be relied on to handle the power that this motor was capable of producing. We built a metal coupler, which included two set screws to make sure the two shafts stayed in place. This is the current configuration and it has worked well ever since we installed it.

The second task we had to complete was to create a system which would be able to run our code and execute servo commands, which would in turn be changed into motor commands and sent to the motor. We decided to take two existing boards, the SerialSense board created by Andrew Chanler and the servo board. The SerialSense board would store and execute our code and send commands to the servo board. The servo board would generate servo commands which would be sent to a variable speed controller. We created a design that would encompass both of these boards on the same board, but when we etched it out we had some problems getting it to work correctly. Running short on time we decided that we would just go with a SerialSense board and a servo board.

Note: For the schematic and board files of our design go to the board directory.

SerialSense Board 

The SerialSense board is a vital component to our steering system, it runs the code for our subsystem and the rear motor subsystem. It is connected to the servo board through the Cricket Bus and to the ITX board through a serial connection. Various commands are sent from the ITX board to the SerialSense and it is up to the program running on the SerialSense to interpret the commands and send out the appropriate signal to the servo board. The SerialSense board is also responsible for monitoring the sensors and making decisions based on the sensor readings. For example if the left touch sensor is triggered then the SerialSense must send a command to the servo board to send a stop signal to the speed controller.

Note: For more information about the SerialSense board go to http://www.cs.uml.edu/~achanler/robotics/serialsense

Servo Board 

The servo board has a simple function, it takes signals from the SerialSense board through the Cricket Bus and translates them into the appropriate servo command. It then takes those servo commands and sends them to the variable speed controller.

Note: For more information about the SerialSense board go to http://www.handyboard.com/cricket/bus/8servo.shtml

Speed Controller 

The speed controller accepts servo commands from the servo board through its Cricket Bus port, and from those commands it generates a set amount of power. The speed controller is hooked up to the steering motor, and when it receives a servo signal it powers the motor until it is told to stop.

Steering Sensors 

The sensor system that we have installed on the MCP to support the steering is designed to limit the motor from turning the wheels too far. We have touch sensors on the left and right side of the MCP which line up with the connection mechanism between the wheels and the steering bar. When the wheel moves to a position which is acceptable to turn but far enough, the sensor is tripped by the connection mechanism. This sends a message to the SerialSense board which then gets interpreted as a stop command. Along with sensors on the sides, there is a "middle" sensor which will tell the MCP that it is going straight, or at least it is approximately straight. This middle sensor is not a touch sensor but rather a magnetic sensor which sends its signal when the magnet on the steering bar moves beneath the magnetic sensor, which is fixed to the chassis.

Steering Configuration 

The current configuration is a motor which is directly connected to the steering shaft using a custom steel part. The ITX board issuing commands to the SerialSense board through a serial cable. The SerialSense board which executes steering commands from the ITX board with additional information from the sensor system, and sends the appropriate signals to the servo board. The servo board which translates SerialSense signals into servo commands, which get sent to the variable speed controller. The speed controller takes the servo commands and powers the steering motor appropriately.

3. Software

Serial Communication 

The first means of communication to the steering subsystem is through a serial cable that connects to the SerialSense board to the ITX computer. The setup and use of the serial protocol can be difficult to manage, but fortunately the SerialSense software written by Andrew Chanler handles this communication. An example of this setup can be seen in listing 3.1:

Code listing 3.1

            #include "serialsense.h"
            
            SerialSense *ss = new SerialSense("/dev/ttyUSB0");
            ss->open();
            
            // SerialSense commands here
            
            ss->close();
          

Once a serial connection is esablished, simple class oriented calls can be made to get values of digital and analog devices connected to the board, as well as issue commands over the Cricket Bus. An example of getting and printing a value of a digital sensor connected to port 8 can be seen in listing 3.2:

Code listing 3.2

            printf("%d\n", ss->digital(8));
          

Additionally, the SerialSense board is cable of sendout out to other devices over a connection known as the Cricket Bus which we use to issue servo commands to the motor. Details on this can be seen in the next section.

Cricket Bus 

The Cricket Bus is an excellent means of connecting two devices together, allowing them to easily talk to each other. Usually commands are issued with simple low level calls, but we are able to simplify this again using the SerialSense environment. For example, sending commands to a servo would originally look like listing 3.3:

Code listing 3.3

            to servopos :id :angle
              bsend $115
              bsend :id * 2 + 1
              bsend :angle
            end
            
            to servooff :id 
              bsend $115
              bsend :id * 2
              bsend 0
            end
          

Instead, simple servo commands can be issued by sending a register and a servo value as in listing 3.4:

Code listing 3.4

            // Send the value 20 to servo over channel 1 (port 0 high)
            ss->servo(1, 20);
          

Note: The servo board we are using has 8 ports. However, each port has two channels designated to it; odd and even channels. It is important to always use the odd numbered channel numbers, as those will carry higher values to the servo. For example, if port 0 is used, channel 1 should be used. Similarly, if port 1 is used, channel 3 should be used.

UDP Communication 

The easiest way to have different subsystems communicate to the Master Control Program (MCP), we use a UDP networking protocol. The choice to use UDP over TCP was to alleviate the overhead of connection setup, especially since packet loss will be very minimal being on the same machine. The UDP protocol is a two part process; a server and a client. Code listing 3.5 shows how to open a server connection using the UDP subsystem:

Code listing 3.5

            int       message;
            char      buf[STEERING_LIMIT];
            sctx      ctx;
            
            if(message = startserver(&ctx, STEERING)){
                perror("Failure initializing context");
                exit(1);
            }
            
            // Insert additional code and message receival here
            
            endserver(&ctx);
          

For the MCP, we use port 9001, which is defined as STEERING. It is important to remember this port number (which can be easily changed in the supplied services.h), since a client will communicate to the server over this port. Also, we use ctx to keep a handle of the UDP instance, and message for error checking. Upon creation of a server, a none zero (0) message means there was a problem.

Once a server is started, we can now accept commands from a client sending commands over the designated port. We do this with the code outlined in listing 3.6:

Code listing 3.6

            bzero(buf, STEERING_LIMIT);
            message = getmsg(&ctx, buf, 400);
          

The first line, bzero, clears our buffer to ensure that are no artifacts from previous entries. This is optional, but recommended to prevent unexpected runtime errors. The second line is where we actually wait for a message, and when one is received, is stored to our buffer. In the case of receiving a message, the function also attempts to send out an ACK packet to acknowledge the receival of the information. For our implimentation, we don't make use of this ACK message. The actual message received is stored to the second argument (buf in our case), which is a character array that is at least the size of argument 3.

Runtime Code 

The steering server in its entirety can be viewed in section 5, which combines many of the previousely outlined components in this section. The basic stucture of the code is as follows:

  • Open SerialSense connection
  • Open UDP port for incoming commands
  • Create a pthread instance for an asynchronous steering loop
  • Begin main loop to accept UDP commands, parsing them appropriately
The opening of both SerialSense and UDP connections have already been outlined, so we can omit further explanation. The reason we want to create a seperate thread for the steering is so no matter what is sent to it, the limit sensors are always being checked. Without this, there is a chance a servo command for a certain direction could be sent before the SerialSense would realize that it is already turned to its limit, potentially causing itself to over torque. By using threads, we simply set the state of the steering (steerState) in the main thread, and the steering thread reacts to the change appropriately.

Essentially, there are only three steering states: left, right, and off. Each of these states are representative of an integer value between 0 and 255, with 128 being off. Values greater than 128 are left (incrementing in power as the number gets larger), and values less than 128 being right (incrementing in power as the number diminishes).

This state is controlled by a client issuing steering commands to the server:
  • left
  • right
  • stop
  • center (not implemented)
  • quit
When a state is changed, it will remain in that state until it is altered by its respective limit sensor or is explicitly changed again by a client. Issuing a quit command will properly end the steering thread, close the server, close the connection to the SerialSense, and exit the program.

4. Figures


Figure 4.1: Original Schematic

Fig. 1: Original Schematic


Figure 4.2: New Motor Mounting

Fig. 2: New Motor Mounting


Figure 4.3: Undercarriage Pin

Fig. 3: Undercarriage Pin


Figure 4.4: Steel Shaft Coupler

Fig. 4: Steel Shaft Coupler


Figure 4.5: Limit Sensor Mount

Fig. 5: Limit Sensor Mount


Figure 4.6: Center Limit Magnet

Fig. 6: Center Limit Magnet

5. Code

Code listing 5.1: steeringServer.c

#include <stdio.h>
#include <pthread.h>

#include "steeringServer.h"

SerialSense *ss;
bool         terminateServer = false;

int  cmdState   = FORWARD,
     speed      = MOTORSOFF,
     steerState = STEER_STOP;

int commandtype;
#define CMD_DONE 0
#define CMD_MOTOR 1
#define CMD_STEER 2

void do_motors() {
  if (cmdState == BACKWARD){
    ss->set(7);   // set port7 high backward
  } else {  
    ss->clear(7); // set port7 low forward
  }
  ss->servo(3, MOTORSOFF + speed * POWERINCREMENT);
  ss->servo(5, MOTORSOFF + speed * POWERINCREMENT);
}

void do_steering() {
  if (steerState < STEER_STOP) { // steering to the right
    if (!(ss->digital(RIGHT_LIMIT)))
      ss->servo(1, steerState);
    else 
      ss->servo(1, STEER_STOP);
  } else if (steerState > STEER_STOP) { // steering to the left
    if (!(ss->digital(LEFT_LIMIT)))
      ss->servo(1, steerState);
    else 
      ss->servo(1, STEER_STOP);
  } else {  // steer stop
    ss->servo(1, STEER_STOP);
  }
}

void testSteerLimits() {
  if (steerState == STEER_CENTER) { // steer to center
    if (ss->digital(CENTER_LIMIT)) {
      ss->servo(1, STEER_STOP);
      steerState = STEER_STOP;
    }
  } else if (steerState < STEER_STOP) { // steering to the right
    if (ss->digital(RIGHT_LIMIT)) {
      ss->servo(1, STEER_STOP);
      steerState = STEER_STOP;
    }
  } else if (steerState > STEER_STOP) { // steering to the left
    if (ss->digital(LEFT_LIMIT)) {
      ss->servo(1, STEER_STOP);
      steerState = STEER_STOP;
    }
  }
}

void *ssTalker(void *threadid) {
  while (1) {
    // when waiting for the server to receive a command,
    // continually check the steering limit switches.
    while (commandtype == CMD_DONE) testSteerLimits();

    if (commandtype == CMD_MOTOR) {
  do_motors();
    }
    else if (commandtype == CMD_STEER) {
  do_steering();
    }
    
    // reset command so it doesn't get sent again
    commandtype = CMD_DONE;
  }
}

void initializeSteeringAndMotors(){
  // turn off steering
  steerState = STEER_STOP;
  ss->servo(1, steerState);

  // turn off rear motors AND
  // enable rear wheel speed controllers by turning on at 0 speed for 3 sec
  speed    = MOTORSOFF;
  cmdState = FORWARD;
  ss->servo(3, speed);
  ss->servo(5, speed);

  // set up relay controller and test it
  ss->setupIO(7, 0);                     // setup port7 as an output
  ss->set(7);                            // set port7 high backward
  sleep(1); 
  ss->clear(7);                          // set port7 low forward

  // 2 more seconds before rear wheel speed controllers will
  // accept commands
  sleep(2);
}

int main(int argc, char *argv[]){
  int       message,
            index,
            rc,
            t = 0;
  char      buf[STEERING_LIMIT];
  sctx      ctx;
  pthread_t ssTalker_t;

  /******* INITIALIZE SERVER *******/
  if(message = startserver(&ctx, STEERING)){
    perror("Failure initializing context");
    exit(1);
  }
  printf("Steering Server initialized with message: %d\n", message);

  // Create a connection to the SerialSense
  ss = new SerialSense("/dev/ttyUSB0");
  ss->open();

  /******* INITIALIZE MOTORS *******/
  initializeSteeringAndMotors();

  /******* CREATE ssTalker THREAD *******/
  commandtype = CMD_DONE;
  rc = pthread_create(&ssTalker_t, NULL, ssTalker, (void *)t);
  if (rc) {
    printf("Error -- could not create ssTalker thread (code %d)\n", rc);
    exit(-1);
  }

  /******* MAIN PROGRAM LOOP *******/
  while(!terminateServer){
    bzero(buf, STEERING_LIMIT);
    message = getmsg(&ctx, buf, 400);

    if(!strcmp(buf, "quit")){
      cmdState = FORWARD;
      speed = 0;
      commandtype= CMD_MOTOR;
      printf("Quitting Steering Server...\n");
      terminateServer = true;
    }
    else if(!strcmp(buf, "init")){
      initializeSteeringAndMotors();
    }
    else if(!strcmp(buf, "left")){
      steerState = STEER_LEFT;
      printf("got left command\n");
      commandtype= CMD_STEER;
    }
    else if(!strcmp(buf, "right")){
      printf("got right domman\n");
      steerState = STEER_RIGHT;
      commandtype= CMD_STEER;
    }
    else if(!strcmp(buf, "steerstop")){
      steerState = STEER_STOP;
      commandtype = CMD_STEER;
    }
    else if(!strcmp(buf, "halt")){  // stop the robot
      cmdState = FORWARD;
      speed = 0;
      commandtype = CMD_MOTOR;
    }
    else if(!strncmp(buf, "forward", 7)){
      // e.g., "forward 5"
      cmdState = FORWARD;
      speed = buf[8] - 48;  // this char should be digit from 0 to 9
      if ((speed < 0) || (speed > 9)) speed = 0;
      commandtype = CMD_MOTOR;
    }
    else if(!strncmp(buf, "backward", 8)){
      // e.g., "backward 10"
      cmdState = BACKWARD;
      speed = buf[9] - 48;  // this char should be digit from 0 to 9
      if ((speed < 0) || (speed > 9)) speed = 0;
      commandtype = CMD_MOTOR;
    }
  }

  endserver(&ctx);

  // wait until ssTalker thread gets to send stop command
  while (commandtype != CMD_DONE);
  ss->servo(1, STEER_STOP);   // turn off steering
  
  ss->close();

  return 0;
}

        

Code listing 5.2: steeringServer.h

#include <stdio.h>   /* Standard input/output definitions */
#include <string.h>  /* String function definitions */
#include <errno.h>   /* Error number definitions */

#include "serialsense.h"
#include "udpserver.h"

// Steering constants
#define STEER_LEFT      180
#define STEER_RIGHT      80
#define STEER_CENTER    256     // logic decides which direction to go!
#define STEER_STOP      128
#define LEFT_LIMIT   13 // steering limit, normally 1
#define RIGHT_LIMIT  11 // steering limit, normally 1
#define CENTER_LIMIT      9

// Motor constants
#define FORWARD          1
#define BACKWARD         0
#define MOTORSOFF        100
#define POWERINCREMENT   9

// steering limit switches
#define LEFT_LIMIT  13  // steering limit, normally 1
#define RIGHT_LIMIT 11  // steering limit, normally 1
#define CENTER_LIMIT     9  // steering centered, 1 if true
        

Note: Our server also shares functionality of the rear motors as well. For more information, please refer to the rear motor control subsystem.

Note: The full code listing can be found in the code directory.

6. To Do List

There are only a few things which need to be done on the steering sub system:

  1. Coding the software to take advantage of the hardware that is set up to find the middle position so that the MCP knows that it is going straight.
  2. Test the bump sensors on the sides to make sure that they will work consistently. This is more of an ongoing testing and monitoring process because they are currently working very well.



line
Updated 2005-05-19
line
Jon Victorine
Author

Zeb Heisey
Author

Matt Penney
Prior Work on Steering

Matteo Forgione
Contributor

line
Summary:  We will describe the different aspects of the steering subsystem of the MCP, including the hardware, software, and algorithms used.
line
Developed by students of the Engaging Computing Group in the Department of Computer Science at the University of Massachusetts Lowell