Lab Exercise 5 - Motion and Distance
Lab Exercise 5 - Motion and Distance
Jack Donovan
Aaron Wilson
Lab Exercise 1:
One of the many forms of motion control is a servo motor. A servo motor is essentially a
DC motor with an embedded closed loop feedback system. The DC motor is controlled with a
PWM signal and is attached to a potentiometer. This potentiometer feeds back to the
microcontroller to record the current position. The onboard embedded software can use this
information to adjust for errors. The servo had an embedded gear reduction to produce higher
torque and not have the DC motor running at very low RPMs.
For this exercise we decided to use the joystick to control the servo motor. Initially, we
were gonna have the joystick control the speed of the servo, but we decided to control the
position instead (much simpler). We use a function to analogRead() the pin attached to one of the
joystick’s axis’ and then map the 10 bit reading to the range of the servo (180 degrees).
As the PWM control of the servo can be quite complicated, we used a built in arduino
library function to control the servo. To initialize you simply create a Servo object and attach the
control pin to the object. Then, it is as simple as using servo.write() with a position to drive the
servo to the desired position. The only errors we had in the programming of this was thinking the
joystick reads back negative values and the servo uses negative positions (neither do that).
Thankfully, with the use of constants that was an easy fix.
/**
Microcontroller Systems Lab 5
Part 1
Jack Donovan
Aaron Wilson
void setup() {
// attached data pin for servo
myServo.attach(servoDataPin);
pinMode(joystickPin, INPUT);
Serial.begin(9600);
}
void loop() {
// read the joystick, command position
static int position = 0;
position = readJoyStick(joystickPin, servoRange);
myServo.write(position);
}
return read;
Lab Exercise 2:
This exercise involved using an ultrasonic sensor to print out the distance it read to an
LCD screen. Thankfully, we previously displayed the distance from an ultrasonic sensor onto a 7
segment display as part of the free task. For this we simply had to scavenge our code for the
ultrasonic sensor and code for an LCD and shove it together.
Reading the distance from an ultrasonic sensor is fairly straightforward. The sensor
consists of a trigger pin and an echo pin. Holding the trigger pin high for 10 microseconds then a
very fast ultrasonic burst out from it. When this pulse is sent the echo pin goes high, then it goes
low when the sensor, well, senses the return of the ultrasonic pulse after it bounced off an object.
The duration the echo pin was high can be returned from the pulseIn() function. Thankfully, we
know the speed of sound at sea level in normal conditions really well (I am in a gas dynamics
class with a midterm coming soon I do not want to think about the speed of sound right now).
Since we know the time the pulse took and the velocity of the pulse, we can calculate the
distance of the object.
As this sensor can read very fast, we use the following code to smooth out what we send
to the LCD:
if (millis() - timeSinceLastDisplay > delayTime) {
distance = findDistance(triggerPin, echoPin);
timeSinceLastDisplay = millis();
}
The LCD uses the lcd.print() function as detailed in previous reports. We did not end up
using the loading bar but we displayed the distance to the screen.
/**
Microcontroller Systems Lab 5
Part 2
Jack Donovan
Aaron Wilson
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// UltraSonic Pins
const int triggerPin = A2;
const int echoPin = A3;
// Sensor Refresh Rate
const int delayTime = 500;
// LCD Stuff
LiquidCrystal_I2C lcd(0x27,16,2); // set the LCD address to 0x27 for a 16
chars and 2 line display
// partial bars for loading bar
byte bar1[8] {B10000,
B10000,
B10000,
B10000,
B10000,
B10000,
B10000,
B10000};
byte bar2[8] {B11000,
B11000,
B11000,
B11000,
B11000,
B11000,
B11000,
B11000};
byte bar3[8] {B11100,
B11100,
B11100,
B11100,
B11100,
B11100,
B11100,
B11100};
byte bar4[8] {B11110,
B11110,
B11110,
B11110,
B11110,
B11110,
B11110,
B11110};
// grab each appropriate bar
byte* cols[4] = {bar1, bar2, bar3, bar4};
void setup() {
// Ultrasonic sensor pins
pinMode(triggerPin, OUTPUT);
pinMode(echoPin, INPUT);
// Serial Monitor
Serial.begin(9600);
// LCD Stuff
lcd.init();
lcd.backlight();
for (int i = 1; i < 5; i++) {
lcd.createChar(i, cols[i-1]);
}
}
void loop() {
static unsigned long timeSinceLastDisplay;
static int distance = 0;
static int numFullBars = 0;
static int partialBar = 0;
// want to only grab a new distance every so often, no delay is too fast
if (millis() - timeSinceLastDisplay > delayTime) {
distance = findDistance(triggerPin, echoPin);
timeSinceLastDisplay = millis();
}
lcd.setCursor(0,0);
lcd.print("Distance: ");
lcd.print(distance);
lcd.setCursor(14,0);
lcd.print("cm");
//lcd.setCursor(0,1);
//numFullBars =
}
Free Task:
For the free task Aaron and I settled on attaching the ultrasonic sensor to a servo and
“mapping” the surroundings to an LED matrix. The LED matrix is 8x8 and uses SPI to
communicate with the board. As it is 8x8, there are 8 “bars” to display a distance level and 8
levels to each bar. As there are 180 degrees in the servo and 8 possible bars, the bar sweeps in 22
degree windows, takes a running average of the distance in that range, and displays it as a bar on
the matrix. The code is simply looping through each window and in each window, looping
through all 22 degrees and using the following code to get a running average and check for
errors:
if (distance < 0 || distance > maxRange) {
distance = maxRange;
}
// only take running average if this isn't the first value
if (j != 0) {
distance = (distance + prevDistance) / 2;
}
// honestly don't need this but wanted to be sure we weren't hitting weird
stuff with no distance
if (distance == 0 ){
distance = 1;
}
The LED matrix is a bit funny. We ended up using the “LEDControl.h” library which was
pretty simple after some examples and documentation reading. The setup was copied from
documentation examples but seems to create on object with the pinout, set the intensity, tell it to
not shut down (interesting), and clear the display. The following command can write a byte to
any row in the array:
lc.setRow(0,i,bar[distance]);
The zero seems to be the address of the matrix, the second argument is the row, and the
third is the byte to be written. The array “bar” stores 9 different bytes, each a different length of
bar, and a blank bar. The distance value in the line above was mapped to a value between 0 and 8
so it can fit on the matrix.
Only real errors were with large/negative distances (which was fixed by the conditionals
above) and forgetting the pinMode() AGAIN (this was especially sad when we figured out what
was wrong). We also needed to add a clearDisplay() at the top of loop() to get rid of any
straggling LEDs.
/**
Microcontroller Systems Lab 5
Free Task
Jack Donovan
Aaron Wilson
An ultrasonic sensor attatched to a servo scans the full range of the servo. For
each row in an LED matrix,
it averages a bunch of distances in that window and prints a bar corresponding to
that distance
#include <Servo.h>
#include "LedControl.h"
const int servoDataPin = A0;
Servo myServo;
const int servoRange = 180;
// UltraSonic Pins
const int triggerPin = A2;
const int echoPin = A3;
// LED matrix size
const int numRows = 8;
const in numCols = 8;
// How many degrees per row of led matrix
const int clicks = 180 / 8;
// max distance we want to check at
const int maxRange = 250;
// led controll library object
LedControl lc=LedControl(6,7,5,1);
// array of loading bar bytes
byte bar[9] {B00000000, B10000000, B11000000, B11100000, B11110000, B11111000,
B11111100, B11111110, B11111111};
void setup() {
myServo.attach(servoDataPin);
// LED initialization
lc.shutdown(0,false);
lc.setIntensity(0,1);
lc.clearDisplay(0);
Serial.begin(9600);
// forgetting these SUCKS
pinMode(triggerPin, OUTPUT);
pinMode(echoPin, INPUT);
}
void loop() {
int position = 0;
int prevDistance = 0;
int distance = 0;
// make sure display is blank and servo is homed.
lc.clearDisplay(0);
myServo.write(0);
NOTEBOOK: