Wallace Autonomous Robot - Part 3 - Add Obstacle Avoidance Sensors

About: Have enjoyed a mostly fun ride in an electronics and software career. Also enjoy latin dancing.

Intro: Wallace Autonomous Robot - Part 3 - Add Obstacle Avoidance Sensors

Hello.

In Part 1 Instructable of Wallace the Robot, we started with an Agent 390 tracked kit or platform from Servocity. We assembled it, added in all of the essential hardware and software to do some basic movements, using the arrow keys on a keyboard under wifi communications.

In Part 2, we did a lot of work to add a basis or foundation (circuit & wiring infrastructure) to enable us to add and use sensors and controls.

All of the previous work seemed especially tedious, and yet Wallace is still only able to move around under user control.

When building a bridge where there was previously none, it takes much more work and effort to get the very first vehicle across, as it does for all of the rest of them. That first one is extremely hard, while all the rest are relatively easy.

And that has been our situation with trying to get Wallace to go from just being a glorified radio-controlled car, to becoming an autonomous vehicle.

With this Instructable, we finally arrive at the point where we can start adding some sensors that will help Wallace interface with the outside world.

No full autonomy yet, but our goal will be to have Wallace at least warn us when we're getting too close to some object as we drive it around.

As we go through this process, we will also add other improvements that we didn't finish from last time.

Hopefully we will also improve the software.

Step 1: First Sensor: Acoustic Distance Indicator

We begin with the HCSR04. There are so many online articles and how-tos about this. Essentially, we need to provide it with 5V, ground,and we will need two grab two of the I2C port extender pins as signals. One will be the trigger output, and the other will be the echo input.

Connecting and using the HCSR04 to Wallace is made easier since we went through the tedious work of separating the Raspberry's 3.3V GPIO pins from everything else that uses 5V. We interface the Raspberry to everything else via a 3V-to-5V, 5V-to-3V bi-directional bus.

Had we not done that, we'd be doing something like adding a resistor, and a 3.3V zener diode possibly each port.

We will be using the wiringPi C library for now, which makes things pretty easy. If you go back to the other instructables that I linked to, you get an idea of how to use this library in C/C++ to communicate with the port extender pins.

The HCSR04 expects both the trigger and echo pins to start low. This code statement

pullUpDnControl(pinNumber,PUD_DOWN);

helps out to bring them low. (the above step may not be absolutely necessary).

One program flow to follow in the software in order to perform a distance-indication or notification, is to:

  • bring trigger output to a low for some brief time
  • bring trigger output high for around 10us (I've succeeded with even 5us)
  • bring trigger output low and immediately
  • in a loop, read echo input to go high (take timestamp at that point)
  • in a loop, read echo input to go low (take timestamp at that point)
  • then do a delta between the two timestamps, and that's your distance indication

I can think of another way as well:

  • for trigger, same as above
  • to read echo, same two loops as above, but don't take a timestamp.

Do we really need a timestamp? All we want is some number that tells us if we are too close, or not.

How about loops that increment some integer variable for every read that is a HIGH, for only an X number of times, or when we read a LOW.

That integer value would reflect relative distance. With some experimenting, you could zero in on what number range mean "too close", and what doesn't.

What I have noticed is that the I2C communications isn't all that fast. Perhaps the alternative might be quicker.

Here is the complete test code for controlling one sensor:

First, the C source:

<p>///////////////////////////////////////////////////////////////////////////////<br>//usage:
//run the program with an integer. that will be the ms on and off pulsing.
//if you add a 2nd parameter of "all", it will cycle through all 16 ports
//of the mcp23017.
///////////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <wiringPi.h>
#include <mcp23017.h></p><p>///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// LOCAL FUNCTION DECLARATIONS
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
static int isNumber(char* number);</p><p>static void displayDistance(int multiplier, int countHighs);</p><p>static int getDistance(int trigger, int echo, int pulseWidth, int dispDistMultiplier, int loops);</p><p>///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// LOCAL FUNCTION DEFINITIONS (some elsewhere in this file)
// just wanted this function to be at top for convenience
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
static int getDistance(int triggerGP_A_or_B, int echoGP_A_or_B, int pulseWidth, int dispDistMultiplier, int loops) {</p><p>	//printf("trigger: %d    echo:  %d\n", triggerGP_A_or_B, echoGP_A_or_B);</p><p>	// init the pair of GPs
	pinMode(triggerGP_A_or_B,OUTPUT);
	pinMode(echoGP_A_or_B,INPUT);</p><p>	// do the trigger pulse to initiate the distance-measurement
	digitalWrite(triggerGP_A_or_B,0);
	delayMicroseconds(pulseWidth);
	digitalWrite(triggerGP_A_or_B,1);
	delayMicroseconds(pulseWidth);
	digitalWrite(triggerGP_A_or_B,0);</p><p>	int startTime=micros();</p><p>	int countHighs = 0;
	for (int i=0;i</p><p>	for (int i=0;i</p><p>	//printf("%d ",countHighs);
	displayDistance(dispDistMultiplier,countHighs);</p><p>	return 0;
}</p><p>///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// START OF MAIN PROGRAM
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
int main(int argc, char** argv) {</p><p>	//////////////////////////////////////////////////////////////////////
	puts("PROCESS & ASSIGN COMMAND-LINE ARGS");
	//////////////////////////////////////////////////////////////////////
	if (8>argc) {</p><p>		puts("Need    
      ");
		puts("\tHelp:");
		puts("\tGPA0 is number 0, and count up from there.");
		puts("\tGPB0 is number 8, and count up from there.");
		puts("\tGPB0 is number 8, and count up from there.");
		puts("\t3rd param is how long to hold trigger pulse high, in microseconds.");
		puts("\t4th param is how long between pulses , in milliseconds.");
		puts("\t5th param is how to display the relative distance on screen.");
		puts("\t6th param is number of loops in the read cycles before aborting getDistance() function.");
		puts("\t7th param is number of times program will run in main loop.");
		return 1;
	}</p><p>	for (int i=0;i</p><p>	if (!isNumber(argv[1])) {</p><p>		puts("Need TRG arg 1 to be a number");
		return 1;
	}</p><p>	if (!isNumber(argv[2])) {</p><p>		puts("Need ECH pin arg 2 to be a number");
		return 1;
	}</p><p>	if (!isNumber(argv[3])) {</p><p>		puts("Need PULS arg 4 to be a number");
		return 1;
	}</p><p>	if (!isNumber(argv[4])) {</p><p>		puts("Need SILEN arg 5 to be a number");
		return 1;
	}</p><p>	if (!isNumber(argv[5])) {</p><p>		puts("Need DIST MULT arg 5 to be a number");
		return 1;
	}</p><p>	if (!isNumber(argv[6])) {</p><p>		puts("Need LOOPS arg 6 to be a number");
		return 1;
	}</p><p>	if (!isNumber(argv[7])) {</p><p>		puts("Need PRGCYCLS arg 6 to be a number");
		return 1;
	}</p><p>	int trigger  = atoi(argv[1]);</p><p>	if (0>trigger || 15</p><p>		puts("Need trigger has to be between [0,15]");
		return 1;
	}</p><p>	int echo  = atoi(argv[2]);</p><p>	if (0>echo || 15</p><p>		puts("Need echo has to be between [0,15]");
		return 1;
	}</p><p>	if (trigger==echo) {</p><p>		puts("Trigger(output) must be different pin from Echo(input).");
		return 1;
	}</p><p>	int pulseWidth = atoi(argv[3]);
	if (1>pulseWidth) {
		puts("Pulse width(microseconds) must be greater ZERO");
		return 1;
	}</p><p>	int interPulseWidth = atoi(argv[4]);
	if (1>interPulseWidth) {
		puts("Inter-pulse delay must be greater than ZERO");
		return 1;
	}</p><p>	int dispDistMultiplier = atoi(argv[5]);
	if (1>dispDistMultiplier) {
		puts("Display Distance Multiplier must be greater than ZERO");
		return 1;
	}</p><p>	int loops = atoi(argv[6]);
	if (1>loops) {
		puts("Loops must be greater than ZERO");
		return 1;
	}</p><p>	int cycles = atoi(argv[7]);
	if (1>cycles) {
		puts("Cycles must be greater than ZERO");
		return 1;
	}</p><p>	//////////////////////////////////////////////////////////////////////
	puts("PORTS INITIALIZATION");
	//////////////////////////////////////////////////////////////////////</p><p>	// pinBase can be anything above 64.
	// This is the pin number that YOU
	// are assigning to the first pin
	// of the I2C port extender.
	//
	//
	int pinBase = 100;
	trigger += pinBase;
	echo += pinBase;</p><p>	// this is the address that you got
	// from doing i2cdetect on the 
	// command line.
	int i2cAddress = 0x20;</p><p>	// this is the normal setup that you need
	// to call to use wiringPi library
	wiringPiSetup();</p><p>	// this init function binds or associates
	// the pinBase that you determined,
	// with the i2cAddress.
	mcp23017Setup(pinBase, i2cAddress);</p><p>	//////////////////////////////////////////
	//
	//From now on in this program, you 
	//just need to refer to the pin numbers
	//
	/////////////////////////////////////////</p><p>	for (int i=0;i</p><p>		int delta = getDistance(trigger,echo, pulseWidth, dispDistMultiplier, loops);</p><p>		//printf("Delta: %d\n",delta);</p><p>		delay(interPulseWidth);
	}</p><p>	return 0;
}</p><p>///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// LOCAL FUNCTION DEFINITIONS
///////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////</p><p>/////////////////////////////////////////////////////////////////////////////
static int isNumber(char* number) {
///////////////////////////////////////////////////////////////////////////////</p><p>	int i = 0;</p><p>	//checking for negative numbers
	if (number[0] == '-') { i = 1; puts("number is negative"); }</p><p>	for (; number[i] != 0; i++) {
		if (number[i] > '9' || number[i] < '0') {
			printf("checking number[%d] : %d\n",i, number[i]);
			if (!isdigit(number[i])) {
				printf("\tnumber[%d] : %d is not number\n",i,number[i]);
				return 0;
			}
		}
	}
	return 1;
}</p><p>///////////////////////////////////////////////////////////////////////////////
static void displayDistance(int multiplier, int countHighs) {
///////////////////////////////////////////////////////////////////////////////</p><p>	//puts("displayDistance()");
	//
	int MAX = 180;</p><p>	char displayLine[MAX+1];</p><p>	memset(displayLine,0,(MAX+1)*sizeof(char));</p><p>	int current = 0;
	int m = 0;
	int c = 0;
	for (m=0;m</p><p>		for (c=0;c</p><p>			// insure we dont walk off end of char array
			if (current>=MAX) { break; }</p><p>			displayLine[current] = '*';</p><p>			current++;
		}
		if (current>=MAX) { break; }
	}</p><p>	printf("m:%d  c:%d cur:%d ",m,c,current);
	puts(displayLine);
}</p>

And the enclosing Bash shell-script:

<p>#!/bin/bash</p><p>read -p "do check test , or go on to round-robin  ?" prompt;
if [ "$prompt" = y ]; 
then
	#########################################################################################
	# this loop isolates one sensor at a time, for a lengthy period, so that it can be
	# checked if it works, so that the right pins on the MCP23017 can be probed, etc.
	#########################################################################################
	trigger=0;
	eccho=$((trigger + 1));
	read -p "start at $trigger $eccho";
	for i in 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;
	do
		sudo ./test.hcsr04.acoustic.sensor $trigger $eccho 10 20 4 20 800;
		read -p "repeat $trigger $eccho , or move on  ? " prompt;
		if [ "$prompt" != y ]; 
		then
			trigger=$((trigger + 2));
			eccho=$((eccho + 2));
		fi;
	done;
fi;</p><p>read -p "do round-robin , or go on  ?" prompt;
if [ "$prompt" = y ]; 
then
	#########################################################################################
	# this is the real test - is it fast enough to detect distances changes (approaching)
	# while doing a round-robin?
	#########################################################################################
	trigger=0;
	eccho=$((trigger + 1));
	while [ 1 ];
	do
		for trigger in 0 2 4 6 8 10 12 14;  
		do
			eccho=$((trigger + 1));
			sudo ./test.hcsr04.acoustic.sensor $trigger $eccho 10 20 4 20 10;
		done;
	done;
fi;</p>

Step 2: Evaluate With Multiple Sensors

Once we have a feel for using and programming one HCSR04, let's try with several.

By doing this we can test and evaluate our thoughts and direction in how to structure the code, we can gain some insight about whether we will get a timely distance indication in any particular direction within the necessary time constraints, and we can see how much current draw there will be.

The MCP23017 also has an INTA and an INTB port, and they can indicate if there's a change in either side A or B of the I/O port banks. We could hook those up to GPIOs on the Raspberry, and the wiringPi library makes it very easy to setup an interrupt function per GPIO port. We could then do the triggering via the I2C, but get the notification to read when interrupted. That means no polling; no busy-read looping.

I tend to always start with the simplest route until proven wrong. It's so easy to over-engineer something. And as we do, it could become harder to RE-engineer in a different direction.

Using the INT ports from the MCP23017 means we take up one or two more ports on the Raspberry.

The code can become more complicated because now we're dealing with handling an interrupt, and what was it that we were doing in the meantime when we were so rudely interrupted? Interrupts are beyond our control; they happen when they happen.

I have used interrupts. I've also used program threads (example: pthreads). I've used signal-handlers. And multi-processing, and inter-process communications. I've done semaphores, locks, etc,etc. But I try never to start there, and never to assume that it has to be that way.

"Polling" seems to have a bad name in many developer/engineer circles. I disagree.

As we proceed down the road of creating a robot, the software will get complicated enough. No need to add to the complexity until we have to.

If you like, here are some articles that take this sensor to another level. I'm not going to do this unless absolutely necessary.

These acoustic sensors do better when detecting hard objects, vs clothing, soft furniture, pets, plants.

The code example I added here helps arrive at helpful configuration values because you can play with the values using the command-line. One idea is to get as quick a distance-reading as possible. You might want to breadboard these sensors and pretend the breadboard is the robot.

In my case, I placed a series of sensors facing various ways on some breadboards.

Observation: Nine HCSR04s in quiescent drew a total of approximately 30mA+.

Regarding the software and its performance, I first took the single-sensor program and used it inside a Bash shell script, in a loop, playing with the parameters to hit each of the eight sensors. Even with the slowness of Bash scripting, it was sufficient to get back around to the same senor with maybe half a second.

I then enhanced the single-sensor program to handle eight sensors and loop through them. Also added in some timing statements. The worst-case was around 250 ms to loop back around to each sensor.

Simply by direct observation, it was clear that the performance of hitting every sensor was quick enough to avoid any obstacle at the near the higher speeds this robot would be moving while inside an apartment.

Also, the code can be tweaked / improved even further. For instance, if the robot is moving mostly forward, only activate or loop through the forward-facing sensors and not all of them. Perhaps even only one sensor.

Here is the code for multiple sensors, and the Bash script, too. The Bash script uses the C program in two ways. The first way is to run only one sensor at a time, for many cycles, so that you can test that each sensor works. The second way is the round-robin cycling of all sensors.

The C code:

<p>///////////////////////////////////////////////////////////////////////////////<br>//usage:
//run the program with an integer. that will be the ms on and off pulsing.
//if you add a 2nd parameter of "all", it will cycle through all 16 ports
//of the mcp23017.
///////////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <wiringPi.h>
#include mcp23017.h></p><p>///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// LOCAL FUNCTION DECLARATIONS
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
static int isNumber(char* number);</p><p>static void displayDistance(int trigger, int echo, int multiplier, int countHighs);</p><p>static int getDistance(int trigger, int echo, int pulseWidth, int dispDistMultiplier, int loops, int distance);</p><p>///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// LOCAL FUNCTION DEFINITIONS (some elsewhere in this file)
// just wanted this function to be at top for convenience
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
static int getDistance(int triggerGP_A_or_B, int echoGP_A_or_B, int pulseWidth, int dispDistMultiplier, int loops, int distance) {</p><p>	//printf("trigger: %d    echo:  %d\n", triggerGP_A_or_B, echoGP_A_or_B);</p><p>	// init the pair of GPs
	pinMode(triggerGP_A_or_B,OUTPUT);
	pinMode(echoGP_A_or_B,INPUT);</p><p>	// do the trigger pulse to initiate the distance-measurement
	digitalWrite(triggerGP_A_or_B,0);
	delayMicroseconds(pulseWidth);
	digitalWrite(triggerGP_A_or_B,1);
	delayMicroseconds(pulseWidth);
	digitalWrite(triggerGP_A_or_B,0);</p><p>	int startTime=micros();</p><p>	int countHighs = 0;
	for (int i=0;i</p><p>	for (int i=0;i</p><p>	if (countHighs</p><p>	return 0;
}</p><p>///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// START OF MAIN PROGRAM
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
int main(int argc, char** argv) {</p><p>	//////////////////////////////////////////////////////////////////////
	puts("PROCESS & ASSIGN COMMAND-LINE ARGS");
	//////////////////////////////////////////////////////////////////////
	if (7>argc) {</p><p>		puts("Need 
        ");
		puts("\tHelp:");
		puts("\t1st param is how long to hold trigger pulse high, in microseconds.");
		puts("\t2nd param is how long between pulses , in milliseconds.");
		puts("\t3rd param is how to display the relative distance on screen.");
		puts("\t4th param is number of loops in the read cycles before aborting getDistance() function.");
		puts("\t5th param is number of times program will run in main loop.");
		puts("\t6th param is print distance line only if less then this number.");
		return 1;
	}</p><p>	for (int i=0;i</p><p>	if (!isNumber(argv[1])) {</p><p>		puts("Need PULS arg  to be a number");
		return 1;
	}</p><p>	if (!isNumber(argv[2])) {</p><p>		puts("Need SILEN arg  to be a number");
		return 1;
	}</p><p>	if (!isNumber(argv[3])) {</p><p>		puts("Need DIST MULT arg  to be a number");
		return 1;
	}</p><p>	if (!isNumber(argv[4])) {</p><p>		puts("Need LOOPS arg  to be a number");
		return 1;
	}</p><p>	if (!isNumber(argv[5])) {</p><p>		puts("Need PRGCYCLS arg  to be a number");
		return 1;
	}</p><p>	if (!isNumber(argv[6])) {</p><p>		puts("Need DISTANCE arg  to be a number");
		return 1;
	}</p><p>	int pulseWidth = atoi(argv[1]);
	if (1>pulseWidth) {
		puts("Pulse width(microseconds) must be greater ZERO");
		return 1;
	}</p><p>	int interPulseWidth = atoi(argv[2]);
	if (1>interPulseWidth) {
		puts("Inter-pulse delay must be greater than ZERO");
		return 1;
	}</p><p>	int dispDistMultiplier = atoi(argv[3]);
	if (1>dispDistMultiplier) {
		puts("Display Distance Multiplier must be greater than ZERO");
		return 1;
	}</p><p>	int loops = atoi(argv[4]);
	if (1>loops) {
		puts("Loops must be greater than ZERO");
		return 1;
	}</p><p>	int cycles = atoi(argv[5]);
	if (1>cycles) {
		puts("Cycles must be greater than ZERO");
		return 1;
	}</p><p>	int distance = atoi(argv[6]);
	if (1>distance) {
		puts("Distance must be greater than ZERO");
		return 1;
	}</p><p>	//////////////////////////////////////////////////////////////////////
	puts("PORTS INITIALIZATION");
	//////////////////////////////////////////////////////////////////////</p><p>	// pinBase can be anything above 64.
	// This is the pin number that YOU
	// are assigning to the first pin
	// of the I2C port extender.
	//
	//
	int pinBase = 100;</p><p>	// this is the address that you got
	// from doing i2cdetect on the 
	// command line.
	int i2cAddress = 0x20;</p><p>	// this is the normal setup that you need
	// to call to use wiringPi library
	wiringPiSetup();</p><p>	// this init function binds or associates
	// the pinBase that you determined,
	// with the i2cAddress.
	mcp23017Setup(pinBase, i2cAddress);</p><p>	//////////////////////////////////////////
	//
	//From now on in this program, you 
	//just need to refer to the pin numbers
	//
	/////////////////////////////////////////</p><p>	int  maxTime = 0;</p><p>	for (int i=0;i</p><p>		int startTime = micros();</p><p>		for (int trg=0;trg<15;trg+=2) {</p><p>			int trigger = trg + pinBase;
			int echo    = trigger + 1;</p><p>			int delta = getDistance(trigger,echo, pulseWidth, dispDistMultiplier, loops, distance);</p><p>			//printf("Delta: %d\n",delta);</p><p>			delay(interPulseWidth);
		}</p><p>		int endTime = micros();</p><p>		int delta = endTime - startTime;</p><p>		if (maxTime</p><p>			maxTime = delta;</p><p>			printf("MaxTime: %d\n",(maxTime/1000));</p><p>		}
	}</p><p>	return 0;
}</p><p>///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// LOCAL FUNCTION DEFINITIONS
///////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////</p><p>//////////////////////////////////////////////////////////////////////////////
static int isNumber(char* number) {
///////////////////////////////////////////////////////////////////////////////</p><p>	int i = 0;</p><p>	//checking for negative numbers
	if (number[0] == '-') { i = 1; puts("number is negative"); }</p><p>	for (; number[i] != 0; i++) {
		if (number[i] > '9' || number[i] < '0') {
			printf("checking number[%d] : %d\n",i, number[i]);
			if (!isdigit(number[i])) {
				printf("\tnumber[%d] : %d is not number\n",i,number[i]);
				return 0;
			}
		}
	}
	return 1;
}</p><p>///////////////////////////////////////////////////////////////////////////////
static void displayDistance(int trigger, int echo, int multiplier, int countHighs) {
///////////////////////////////////////////////////////////////////////////////</p><p>	//puts("displayDistance()");
	//
	int MAX = 180;</p><p>	char displayLine[MAX+1];</p><p>	memset(displayLine,0,(MAX+1)*sizeof(char));</p><p>	int current = 0;
	int m = 0;
	int c = 0;
	for (m=0;m</p><p>		for (c=0;c</p><p>			// insure we dont walk off end of char array
			if (current>=MAX) { break; }</p><p>			displayLine[current] = '*';</p><p>			current++;
		}</p><p>		if (current>=MAX) { break; }
	}</p><p>	printf("trg:%d  ech:%d cur:%d ",trigger,echo,current);
	puts(displayLine);
}</p>

The Bash shell-script:

<p>#!/bin/bash</p><p>read -p "do check test , or go on to round-robin  ?" prompt;
if [ "$prompt" = y ]; 
then
	#########################################################################################
	# this loop isolates one sensor at a time, for a lengthy period, so that it can be
	# checked if it works, so that the right pins on the MCP23017 can be probed, etc.
	#########################################################################################
	trigger=0;
	eccho=$((trigger + 1));
	read -p "start at $trigger $eccho";</p><p>	# there are extra loops in case need to repeat. loops are not equiv to qty of sensors
	for i in 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;
	do
		sudo ./test.hcsr04.acoustic.sensor $trigger $eccho 10 20 4 20 800;
		read -p "repeat $trigger $eccho , or move on  ? " prompt;
		if [ "$prompt" != y ]; 
		then
			trigger=$((trigger + 2));
			eccho=$((eccho + 2));
		fi;
		if [ $eccho -ge 15 ];
		then
			echo "Have reached last sensor.";
			break;
		fi;
	done;
fi;</p><p>read -p "do round-robin , or go on  ?" prompt;
if [ "$prompt" = y ]; 
then
	#########################################################################################
	# this is the real test - is it fast enough to detect distances changes (approaching)
	# while doing a round-robin?
	#########################################################################################
	while [ 1 ];
	do
		sudo ./test.hcsr04.round.robin 10 20 4 30 10 10;
	done;
fi;</p>

Step 3: Designing Layout to Avoid Obstacles

Once we have some confidence that we can wire these sensors and interact with them, and have some familiarity with the software required, then we shift our focus to a more concrete phase.

The question becomes, how many sensors do we need, and how would they be placed?

Obviously, to watch for obstacles as Wallace moves forward or backward, we would need to add sensors (perhaps two total) front and rear. But it's a bit more complicated than that. One of the easiest ways for a tracked robot to turn is not in a circle, but instead it pivots on its Z (vertical) axis. That is, it does not have to travel in an arc (mostly forward motion) to turn.

Due to this type of movement and being that the Agent 390 platform is rather wide, its corners can swing around and cover a lot of ground. That implies that perhaps we need two sensors at each corner (eight sensors), facing away from each other.

An alternative layout could reduce the number of sensors to four, if we placed one at each corner, mounted on servos that constantly arc back and forth, seeking obstacles.

Which idea to go with, whether eight fixed sensors, or four roving sensors, leads us to the number of pins and I/O ports. But not just that. We are also faced with signal types. And we have to consider how much current is drawn, and whether we can reduce that current by somehow disabling each sensor when not in use.

Scenario #1: Eight Acoustic Sensors, Two Each Corner, Individual Ports

  • that means we need 8 trigger ports, 8 echo ports => 16 ports => one I2C MCP23017 chip taken

Scenario #2: Eight Acoustic Sensors, Two Each Corner, Combine All Triggers, Combine 4 Echos or Combine 8 Echos

  • that means possibly collapse ports down to 2 or 3. One for the Trigger, and one for 4 Echos or 8 Echos

Scenario #3: Four Acoustic Sensors, Four Servos, All Individual Ports

  • that means 4 trigger, 4 echo, 4 PWM

Scenario #4: Four Acoustic Sensors, Four Servos, Combined signals

  • this one means somehow tying signals together. If you take a look at one of the images in this step, it shows how to do that with some diodes a resistor.

I am sure someone can conjure even more scenarios than these.

Let's discuss the above scenarios a bit further.

Scenario #1 takes a lot of sensors and ties up a lot of pins. However, it gives us a lot of individual control. It also means that we know which sensor's echo input gave us a distance reading. How much total current draw is an open question, but each sensor draws a lot less when it is not being triggered. One breadboard test I did using a power-supply at 5V, indicated mA in the teens (~.01) when the sensors were not being triggered. My power-supply's current meter reading is only to tenths of amps, or tens of milliamps.

Scenario #2 takes the same sensors, but we save on ports. If we combine multiple triggers, then that number of sensors fire an echo. Depending on how we combined them and their physical location and orientation, we have a few different problems. The first is acoustic interference. The sonar sound reflection from one could be detected by another. This may or may not be an issue. Let's say that two sensors at the same corner interfered with each other. That might be ok - it means something is close in that corner. It might not be ok - we might not know if we are heading into problem or away from it. A second problem is that multiple echos bringing the same echo input port high for some length of time, and the longest one will mask shorter ones. So we might be reading only the farthest distance, when what we might be interested in is the closest distance. Yet another problem could be that we don't even know which direction, because say that one front sensor fired, and one rear sensor fired. How to determine whether we are close to something in the front or in the rear?

All the above doesn't mean we shouldn't try this scenario. Perhaps some limited version, or some creative layout will work for our purposes.

My guess, though, is that the more we combine, the less useful information we will receive in order to be able to move around intelligently and not be "frozen with fear". :-)

Scenario #3 (#4 is similar) is an intriguing one for me. In fact, we might be able to collapse that one down to an acoustic sensor / servo pair directly in front, and one in the rear. I like that low number of devices and ports.

Some possible issues with using servos is that (my experience is with the sg90) and from what I recall, the PWM signal has to always be present if you don't want the servo to keep swinging around to some default position. That is, applying some PWM to move it to some position, and then removing the PWM, was the equivalent of a zero % duty cycle, and that causes it to move full (left, I think). If this is true, then there's constant current draw with the PWM and in the servo, that may not be insignificant over time.

There's another issue that we haven't discussed. How high should these sensors be? Wallace is not only very wide, but it is tall. And it will probably only get taller with time, as different components are added. If we put the sensors too low, will they miss something that could crash into the upper parts of the robot?

And finally, what I think is even more important but that will not be an issue for some time yet: Not only are these sensors needed for avoiding obstacles. They also might become very helpful to describe an image of the world to Wallace. Later on, not only do we want to avoid hitting something. We would like Wallace to learn the layout of the apartment.

My conclusion is that I really do not yet know what may prove to be the best course to take. Thus, just like I tried to do when constructing all of the circuits on breakout boards, I want to construct these sensors on their brackets, and with wiring, in such a way that I can make changes at will.

At the moment, though, we do have some good information to make a choice.

We know that eight or nine sensors draw about 30mA total.

From some online research, we know that sg90 servos can draw between 500mA to maybe 1A, depending on load.

We know that the servos will require PWM signals. We know that in order to get a steady servo (no jitter), we'll most like need to use the dedicated PWM pins from the Raspberry, instead of trying to create our own PWM (bit-banging). We also know that the Raspberry has only one PWM pin by default, we would have to enable the second one. Or we could buy a multi-servo board.

For now, just the fact that the current consumption is so much higher, I'm going to start with just the eight HCSR04 sensors in a static position. I want to reserve the PWM for later.

Step 4: Let's Add Notification

Ok, so Wallace can now tell when it is approaching something. Remember that it will do better at detecting some objects than others. Also, since it is quite a wide-base, it may entirely miss an object because it was off to one side of the sensing field. This is all part of an ongoing process to improve Wallace's abilities.

We're not ready to have Wallace go autonomous. We will still be the driver.

How do we know when we're too close to something? Let's add some sort of indicator. I thought of LEDs, but Wallace may be too far away from us for that.

One solution is to add a buzzer (or two?). These can be very simple devices. You just apply power to them and they buzz. I happened to have a couple, so I tried them out with 5V. I noticed that by putting them in series with a resistor, I could still hear them and conserve battery power by lowering the current as much as possible.

You can take yet another port extender pin as an output for this buzzer. Connect the buzzer's (+) pin to 5V, the other pin to the resistor, and the resistor to the port extender pin (which you'll set as output). This time, using wiringPi library, set the pullUpDnControl to a HIGH. That means no buzzer sound.

I noticed that the active buzzer had a louder sound, and could accept a larger-value resistor (less current) than the passive one.

The difference between a passive and an active buzzer is that with the former, you will need to provide the oscillating signal to produce sound, whereas the latter has its own sound source.

With an active buzzer, just do a digitalWrite(low) at the I/O pin, and it should sound until you do a digitalWrite(high). With a passive buzzer, you'll need to create a pulse at the I/O pin.

They each have their advantages and disadvantages. The active buzzer doesn't need a pulse, meaning that the software doesn't have to waste cycles creating a pulse. However, you (mostly) get a single-pitch sound from it. The passive buzzer is more flexible, and you can vary the frequency of the pulses to create different sounds, however that means the software has to spend precious time doing it.

One thing I did notice was you can still make the active buzzer sound different by pulsing it as well. In effect, you're just turning its own sound on and off at different speeds.

One notification purpose and technique is that as acoustic sensors start detecting an object, the software uses the buzzer to notify. The complication is if/when it has to somehow relate the buzzer sound to the closeness of the object.

But First, Let's Free Ports

Before we can add a buzzer notification, we have a dilemma. We have run out of ports of the one I2C MCP23017 port expander circuit. We have at least three choices.

Just use one of the normal GPIO pins on the Raspberry Pi.

Go ahead and add the second I2C circuit that we started.

How about collapsing all HCSR04 echo signals to a single I2C input port, and individually control the trigger signals.

(Update: Using the ORed sensor-echos with diodes and a resistor approach proved to be touchy / flaky long-term for me. Using an oscilloscope, the voltage levels and pulses were not good.... and I'm not sure if one of the diodes partially (is that possible?) failed. In any case, I did get it to work for a while but then it failed. One temporary improvement was to momentarily set the Echo pin to output on the MCP23017, before setting it back to input. But finally, this approach just quit working for me.

My next approach was going to OR the echos the "right" way and use some logic OR gates, but for now I didn't want to have to add more chips/circuits. So I am punting and going back to having to monitor each Trigger/Echo individually. So with one chip, I can control eight HCSR04s, which might be enough, since I am now also using a couple of Sharp IR sensors). One change I made was to swap out the MCP23017 with an MCP23S17. The "S" version uses SPI instead of I2C signalling. My hope is that it will be faster.)

In our round-robin test software, not much would change. The only change would be getDistance() function would just always be passed individual trigger port numbers, and read from the same echo port, no matter which sensor we're polling.

When the function does the trigger pulsing, only the triggered sensor would respond, but the function will only read and be interested in one of them at any given moment in the round-robin loop.

It won't shorten the loop time, but it does free up seven ports that were previously used for triggering.

Note, I had read some time back about combing ports even more. Have one pin be both the trigger AND the echo for the same sensor. I was unable to get that to work. However, it would really only buy us just one more pin. Not worth the trouble.

<p>///////////////////////////////////////////////////////////////////////////////<br>//usage:
//run the program with an integer. that will be the ms on and off pulsing.
//if you add a 2nd parameter of "all", it will cycle through all 16 ports
//of the mcp23017.
///////////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <limits.h>
#include <wiringPi.h>
#include <mcp23017.h></p><p>///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// LOCAL FUNCTION DECLARATIONS
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
static int isNumber(char* number);</p><p>static void displayDistance(int trigger, int echo, int multiplier, int countHighs);</p><p>static int getDistance(int trigger, int echo, int pulseWidth, int dispDistMultiplier, int loops, int distance);</p><p>///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// LOCAL FUNCTION DEFINITIONS (some elsewhere in this file)
// just wanted this function to be at top for convenience
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
static int getDistance(int trigger, int echo, int pulseWidth, int dispDistMultiplier, int loops, int distance) {</p><p>	pinMode(trigger,OUTPUT); 
	pinMode(echo,INPUT); </p><p>	// do the trigger pulse to initiate the distance-measurement
	digitalWrite(trigger,0);
	delayMicroseconds(pulseWidth);
	digitalWrite(trigger,1);
	delayMicroseconds(pulseWidth);
	digitalWrite(trigger,0);</p><p>	int countHighs = 0;</p><p>	int value = 0;
	for (int i=0;i</p><p>	for (int i=0;i</p><p>	if (countHighs</p><p>	return 0;
}</p><p>///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// START OF MAIN PROGRAM
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
int main(int argc, char** argv) {</p><p>	//////////////////////////////////////////////////////////////////////
	puts("PROCESS & ASSIGN COMMAND-LINE ARGS");
	//////////////////////////////////////////////////////////////////////
	if (6>argc) {</p><p>		puts("Need 
       ");
		puts("\tHelp:");
		puts("\t1st param is how long to hold trigger pulse high, in microseconds.");
		puts("\t2nd param is how long between pulses , in milliseconds.");
		puts("\t3rd param is how to display the relative distance on screen.");
		puts("\t4th param is number of loops in the read cycles before aborting getDistance() function.");
		puts("\t5th param is number of round robins (of all sensors) in main loop.");
		puts("\t6th param is print distance line only if less then this number.");
		return 1;
	}</p><p>	for (int i=0;i</p><p>	if (!isNumber(argv[1])) {</p><p>		puts("Need PULS arg  to be a number");
		return 1;
	}</p><p>	if (!isNumber(argv[2])) {</p><p>		puts("Need SILEN arg  to be a number");
		return 1;
	}</p><p>	if (!isNumber(argv[3])) {</p><p>		puts("Need DIST MULT arg  to be a number");
		return 1;
	}</p><p>	if (!isNumber(argv[4])) {</p><p>		puts("Need LOOPS arg  to be a number");
		return 1;
	}</p><p>	if (!isNumber(argv[5])) {</p><p>		puts("Need ROUNDROBINS arg  to be a number");
		return 1;
	}</p><p>	if (!isNumber(argv[6])) {</p><p>		puts("Need DISTANCE arg  to be a number");
		return 1;
	}</p><p>	int pulseWidth = atoi(argv[1]);
	if (1>pulseWidth) {
		puts("Pulse width(microseconds) must be greater ZERO");
		return 1;
	}</p><p>	int interPulseWidth = atoi(argv[2]);
	if (1>interPulseWidth) {
		puts("Inter-pulse delay must be greater than ZERO");
		return 1;
	}</p><p>	int dispDistMultiplier = atoi(argv[3]);
	if (1>dispDistMultiplier) {
		puts("Display Distance Multiplier must be greater than ZERO");
		return 1;
	}</p><p>	int loops = atoi(argv[4]);
	if (1>loops) {
		puts("Loops must be greater than ZERO");
		return 1;
	}</p><p>	int robins = atoi(argv[5]);
	if (1>loops) {
		puts("Robins must be greater than ZERO");
		return 1;
	}</p><p>	int distance = atoi(argv[6]);
	if (1>loops) {
		puts("Distance must be greater than ZERO");
		return 1;
	}</p><p>	//////////////////////////////////////////////////////////////////////
	puts("PORTS INITIALIZATION");
	//////////////////////////////////////////////////////////////////////</p><p>	// pinBase can be anything above 64.
	// This is the pin number that YOU
	// are assigning to the first pin
	// of the I2C port extender.
	//
	//
	int pinBase = 100;</p><p>	// this is the address that you got
	// from doing i2cdetect on the 
	// command line.
	int i2cAddress = 0x20;</p><p>	// this is the normal setup that you need
	// to call to use wiringPi library
	wiringPiSetup();</p><p>	// this init function binds or associates
	// the pinBase that you determined,
	// with the i2cAddress.
	mcp23017Setup(pinBase, i2cAddress);</p><p>	//////////////////////////////////////////
	//
	//From now on in this program, you 
	//just need to refer to the pin numbers
	//
	/////////////////////////////////////////</p><p>	int  maxTime = 0;
	int  echo = pinBase; // 1st pin on MCP23017</p><p>	for (int rounds=0;rounds</p><p>		int startTime = micros();</p><p>		//this loop goes ONCE for each sensor
		for (int trigger=(1+pinBase);trigger<(9+pinBase);trigger++) {</p><p>			getDistance(trigger, echo, pulseWidth, dispDistMultiplier, loops, distance);</p><p>			delay(interPulseWidth);
		}</p><p>		int endTime = micros();
		int delta   = endTime - startTime;
		if (maxTime</p><p>	return 0;
}</p><p>///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// LOCAL FUNCTION DEFINITIONS
///////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////</p><p>//////////////////////////////////////////////////////////////////////////////
static int isNumber(char* number) {
///////////////////////////////////////////////////////////////////////////////</p><p>	int i = 0;</p><p>	//checking for negative numbers
	if (number[0] == '-') { i = 1; puts("number is negative"); }</p><p>	for (; number[i] != 0; i++) {
		if (number[i] > '9' || number[i] < '0') {
			printf("checking number[%d] : %d\n",i, number[i]);
			if (!isdigit(number[i])) {
				printf("\tnumber[%d] : %d is not number\n",i,number[i]);
				return 0;
			}
		}
	}
	return 1;
}</p><p>///////////////////////////////////////////////////////////////////////////////
static void displayDistance(int trigger, int echo, int multiplier, int countHighs) {
///////////////////////////////////////////////////////////////////////////////</p><p>	//puts("displayDistance()");
	//
	int MAX = 180;</p><p>	char displayLine[MAX+1];</p><p>	memset(displayLine,0,(MAX+1)*sizeof(char));</p><p>	int current = 0;
	int m = 0;
	int c = 0;
	for (m=0;m</p><p>		for (c=0;c</p><p>			// insure we dont walk off end of char array
			if (current>=MAX) { break; }</p><p>			displayLine[current] = '*';</p><p>			current++;
		}</p><p>		if (current>=MAX) { break; }
	}</p><p>	printf("trg:%d  ech:%d cur:%d ",trigger,echo,current);
	puts(displayLine);
}</p>

Now we have room for buzzers.

Yay, Time For Buzzing

Here is the C code for a simple buzz test. The code can be used to toggle a single I2C output port.

<p>#include <stdio.h><br>#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <limits.h>
#include <wiringPi.h>
#include <mcp23017.h></p><p>///////////////////////////////////////////////////////////////////////////////
static int isNumber(char* number) {
///////////////////////////////////////////////////////////////////////////////</p><p>	int i = 0;</p><p>	//checking for negative numbers
	if (number[0] == '-') { i = 1; puts("number is negative"); }</p><p>	for (; number[i] != 0; i++) {
		if (number[i] > '9' || number[i] < '0') {
			printf("checking number[%d] : %d\n",i, number[i]);
			if (!isdigit(number[i])) {
				printf("\tnumber[%d] : %d is not number\n",i,number[i]);
				return 0;
			}
		}
	}
	return 1;
}</p><p>///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// START OF MAIN PROGRAM
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
int main(int argc, char** argv) {</p><p>	if (4>argc) {</p><p>		puts("need   ");
		return 1;
	}</p><p>	if (!isNumber(argv[1])) {</p><p>		puts("ON ms must be a number");
		return 1;
	}</p><p>	if (!isNumber(argv[2])) {</p><p>		puts("OFF ms must be a number");
		return 1;
	}</p><p>	if (!isNumber(argv[3])) {</p><p>		puts("LOOPS must be a number");
		return 1;
	}</p><p>	int onMs = atoi(argv[1]);
	int offMs = atoi(argv[2]);
	int loops = atoi(argv[3]);</p><p>	int pinBase = 100;
	int i2cAddress = 0x20;
	wiringPiSetup();
	mcp23017Setup(pinBase, i2cAddress);</p><p>	int buzzerOutput = pinBase + 9;</p><p>	pinMode(buzzerOutput,OUTPUT); 
	pullUpDnControl(buzzerOutput,PUD_UP);</p><p>	for (int i=0;i</p><p>		digitalWrite(buzzerOutput,0);
		delay(onMs);
		digitalWrite(buzzerOutput,1);
		delay(offMs);
	}</p><p>	digitalWrite(buzzerOutput,1);
	return 0;
}</p>

And here is the Bash shell script wrapper:

<p>#!/bin/bash</p><p>onMs=1;
offMs=200;
loops=10;</p><p>while [ 1 ];
do
	echo "offMs = $offMs";
	sudo ./test.mcp23017.toggle.one.output $onMs $offMs $loops;
	onMs=$((onMs+2));
	offMs=$((offMs-20));
	if [ $offMs -le 1 ]; then break; fi;
done;</p> 

Now we want to combine the operation of the acoustic sensors with the warning of the buzzer. This next C program uses pthreads to run two threads. One thread just toggles an I2C output pin that causes the buzzer to buzz, and the other toggles the trigger pin of the HCSR04.

<p>#include <stdio.h><br>#include <stdlib.h>
#include <pthread.h>
#include <wiringPi.h>
#include <mcp23017.h></p><p>typedef struct {</p><p>	char* message;
	int   pin;
	int   millis;</p><p>} ToggleParams;</p><p>void* toggle(void* in) {</p><p>	ToggleParams* params = (ToggleParams*)in;
	char* message = params->message;
	int pin = params->pin;
	int millis = params->millis;</p><p>	for (int i=0;i<20;i++) {</p><p>		printf("%s\n",message);
		digitalWrite(pin,1);
		delay(millis);
		digitalWrite(pin,0);
		delay(millis);
	}</p><p>}</p><p>int main() {</p><p>	int pinBase = 100;
	int i2cAddress = 0x20;
	int pinOutput1 = pinBase + 1;
	int pinOutput9 = pinBase + 9;</p><p>	wiringPiSetup();
	mcp23017Setup(pinBase,i2cAddress);
	pinMode(pinOutput1,OUTPUT);
	pinMode(pinOutput9,OUTPUT);</p><p>	pthread_t thread1, thread2;</p><p>	ToggleParams params1;
	params1.message = "toggling HCSR04 trigger";
	params1.pin     = pinBase+1;
	params1.millis  = 10;</p><p>	ToggleParams params2;
	params2.message = "toggling buzzer";
	params2.pin     = pinBase+9;
	params2.millis  = 20;</p><p>	pthread_create(&thread1,NULL,toggle,(void*)¶ms1);
	pthread_create(&thread2,NULL,toggle,(void*)¶ms2);</p><p>	pthread_join(thread1,NULL);
	pthread_join(thread2,NULL);</p><p>	digitalWrite(pinOutput1,1);
	digitalWrite(pinOutput9,1);</p><p>}</p>

Ok, finally we arrive at the purpose of this section - to get the buzzer to warn us if the acoustic sensor encounters an obstacle.

You can check out the last videos for that part, and I also attached the source to this step.

Step 5: From Breadboard to Circuit Boards

Just like was done in Part 2, we move the sensors on to something more permanent and useful.

Then we can more easily do some mockup layouts as we get familiar with how all of this might work together.

We can try different positions with these sensors and approach them at different angles.

We construct these sensor breakout boards in such a way to make them easily changeable to another sensor (in case it fails or it's not giving good readings), or to be able to mount them directly (static) on the robot frame, or to be mountable on a servo later if we want. Also, we can easily mount them high or low.

In the case of these acoustic sensors, our "circuit board" is nothing more than a way to mount the sensor, and a way to connect wires. In a previous project, since I did not use a 3V-to-5V bi-directional bus level-shifter, these acoustic sensor circuit boards were a bit bigger and more complicated. I had to add two resistors and two 3.3V zener diodes.

These sensors tend to have forward-weight. So I drilled a hole just in front of them, trying to find the center of gravity. Of course, the wires will affect that.

Step 6: Time to Use Acoustic Distance Detection and Notification

If you take a look at the first video, it's a simple test that extends what we did in the previous step (when we ran the code using a single sensor, and the buzzer) and does that with all eight sensors in a round-robin.

The program has two threads again. One thread handles doing the buzzing by watching some shared "distance" variable, and the other thread does the round-robin trigger and echo of the eight sensors, and updates the same "distance" variable.

You'll notice that the buzzing seems rather sporadic, even if it does increase or decreased based on distance.

One reason for that is that the Raspbian version I'm using (stock) is not real-time. However, we might be able to improve that by:

1) isolating a cpu core to that program , (or maybe two cores, one for each thread?)

2) change the scheduling policy and increase the priority of those threads

However, I don't think buzzing should be that high of a priority. What's really important is that the robot be quickly aware of an impending crash.

Step 7: Combine Distance Detection With Robot Movement (Avoidance)

I think the next logical (and easier) test is to construct a program where the robot is stationary. It only moves (away from) when a sensor detects a diminishing distance; when my hand or something approaches.

If I approach from the front, it should of course move back. If I approach from the front-left side, it should rotate right.

This is not so simple. I can think of at least two questions or concerns or things to take into account.

  • should it move away at varying speed depending on distance? Meaning, as it moves away, if the distance increased, should it slow and finally stop (sounds like, yes, obviously).
  • what happens if it encounters a closing distance from another direction because it was moving away from the first one? Example: it was rotating / pivoting away from something on it's front-left side, but if it keeps rotating away, it will encounter the very same object with its rear-left side.

Here's yet another thought. Could it get into some oscillating mode where it finds itself moving back and forth (whether front to back, or rotating clockwise / counterclockwise) ? I can see that it could easily happen. We should code for that, where it keeps track of repeating opposite movements within a certain time period. In other words, it should realize when it is "stuck". One solution to that is just to cease moving. Perhaps do some other type buzzing sound pattern (and LED?) to let us know that it's "stuck".

Movement

Before we can really test any object-avoidance, we need to improve our basic robot movement.

At this phase in the project, the robot has no notion of location, orientation, or movement.

Since it came with the stock motors, they don't have encoders that could help with knowing about movement.

We also don't have an IMU (inertia measurement unit) sensor. We don't have any time-of-flight laser sensor either.

Some things that could help us get an idea of movement, would be:

  • track time (milliseconds)
  • track history of duty-cycle value and its changes
  • track history of amount of current draw and its changes

Tracking time - you take a snapshot of the time before starting a movement, and keep track of X number of milliseconds that have elapsed, to know when to stop moving. This would be combined with the other two.

Duty cycle - The duty cycle is directly proportional to how fast the motor(s) might be turning. It's not a guarantee, but if the robot is not stuck, and the inside apartment flooring isn't too restrictive, duty cycle might actually be helpful. You could do this by trial and error, and get some useful data, where you can measure distances traveled, or rotation angles, based on a given duty cycle for a given number of milliseconds.

Current draw - the Roboclaw can give current draw of each motor. Current draw can go up for two reasons. Either the motors are turning too slowly, or the robot has come up against an obstacle, and isn't moving.

Side note: I think the stock motors are too high an RPM for indoors. They're much faster than they need to be, and they draw too much current at very slow speed. A much lower RPM motor would be better.

With a tracked robot, you can have it turn in two ways. It can describe an arc, or it can pivot/rotate in place. The "normal" arcing movement looks more "natural" and is more cool :-) but I think the rotation in place will be easier later when Wallace needs to know position, location, etc. Later we could move on to the arcing.

Arcing vs rotation means that with the first way, each motor's duty cycle is not the same (regardless of + or -). One would be moving faster than the other. Whereas with rotation, the duty cycles are effectively equal but opposite in sign.

During testing and development, it might be easier and more instructive to not use the initial program that we started with from Part 1 because that one is based on the user driving via keyboard arrow-keys. That program was just a quick way to start using the robot. It adjusts the duty cycles up or down depending on which arrow key was pressed.

A much better-written program would have functions in it like "moveForward()", "moveReverse()", "rotate()" (or "rotateLeft() / rotateRight()" ).

Also, that initial program has a constant loop, and a software watchdog, and looks complicated.

We want to test movement vs sensor-detection, so why not start simple and small, and build up from there.

Final Thoughts

During this phase, several ideas were reinforced.

  • Most HCSR04 sensors, especially from some place like Amazon, are really cheap. By "cheap", I mean unreliable. I am not suggesting not to buy them. On the contrary, do buy them, but try to find a deal with as many as possible, and be ready to encounter several faulty ones. Some may not work at all, or work but not give very good data.
  • Electrical connections are key. Not having that will drive you bananas. If you notice the pictures associated with this step, I did a Round #2 with creating some sort of mounting hardware or breakout board for these sensors. Compare these pictures with some from earlier steps, and the key differences is the use of terminal blocks with tightening screws for the connections.
  • Finally, in the C++ program loop that does the triggering and listening for the echos back for each sensor, I added a new mini-code block that momentarily turns the acoustic Echo (input) into an output, before turning it back into an input. With minute delays throughout.

So the loop (for one sensor, example) goes like so:

-- set both trigger and echo to outputs (new step)

-- delay (new step)

-- set echo to input (new step)

-- delay (new step)

start real job : send trigger (high, delay, low), then wait for echo

I was concerned that this new code would slow down the reaction, but as you can see from the videos, the robot has good response.

Next steps for this robot project would be:

-- continue to improve the code for robustness

-- begin giving the robot some basic driving instructions (default would be to aimlessly wander, but keep a forward direction, without running into anything)

-- take a look at infrared distance sensors

-- begin the process of adding an IMU (or two) for position, location, movement information

Thank you.

Share

    Recommendations

    • Fix It! Contest

      Fix It! Contest
    • Audio Contest 2018

      Audio Contest 2018
    • Furniture Contest 2018

      Furniture Contest 2018

    Discussions