Steering Subsystem
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 |
 |
Figure 4.2: New Motor Mounting |
 |
Figure 4.3: Undercarriage Pin |
 |
Figure 4.4: Steel Shaft Coupler |
 |
Figure 4.5: Limit Sensor Mount |
 |
Figure 4.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:
-
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.
-
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.
|
 |
|
Updated 2005-05-19 |
 |
Jon Victorine
Author
Zeb Heisey
Author
Matt Penney
Prior Work on Steering
Matteo Forgione
Contributor
|
 |
|
Summary:
We will describe the different aspects of the steering subsystem of the MCP,
including the hardware, software, and algorithms used.
|
 |
|