Introduction: Contest Entry: Bacterio-Bot

This instructable shows how to use the iRobot Create to make Bacterio-Bot, a robot that demonstrates the simple navigation behavior of taxis, i.e., following a sensory signal gradient to the signal source. This basic behavior is common even in simple organisms such as bacteria as well as other motile cells. Bacterio-Bot may not seem too sophisticated, but its behavior is the first step in evolving autonomous, complex navigation capabilities. Future work with Bacterio-Bot will include evolving programs using the Avida digital evolution software and translating them onto the robot. The evolution of these programs is ongoing, but the programs are not yet stable enough to use on the robot. The program given here is a hand-coded version of the behavior that I am trying to evolve, using instructions that are analogous to a subset of Avida instructions.

Bacterio-Bot uses a light sensor and implements the "tumble and run" biased random walk algorithm that is used by organisms such as E. coli. Although those types of organisms are most often following chemical gradients, Bacterio-Bot follows a light gradient instead. The robot samples the current light level; if the situation is improving -- if the light is stronger -- the robot will continue moving in the same direction; otherwise, the robot will turn to a new, random orientation and move in that direction. Only very modest "memory" is needed for the algorithm, since only one prior sensory reading needs to be stored for comparison with the current one.



Special thanks to Wesley R. Elsberry for help, especially with hardware and photography.

Step 1: Hardware

Hardware
4th wheel for the back of the Create robot
Materials for light sensor:
(1) DB-9 subminiature male solder-cup connector (~$2.00 per unit in single quantity)
(1) Cadmium sulfide (CdS) photo-cell (~ $2.50 / pack of 5 sensors at Radio Shack)
(1) 5K ohm potentiometer (~$2.00 for assorted potentiometers at Radio Shack)
(1) small piece of perfboard sufficient to hold photo-cell and potentiometer (~$5.00 for larger piece)
(3) 12" pieces of hook-up wire (~$6.00 for 60' of 22 gauge hook-up wire at Radio Shack)

(1) soldering iron
Solder

Step 2: Making the Light Sensor

1) Connect one lead of photo-cell to 5V power (pin 4 of DB-9 connector) with wire.
2) Connect other lead of photo-cell to middle pin on potentiometer.
3) Connect wire to middle pin of potentiometer, then to ADC input pin (pin 1 on DB-9 connector).
4) Connect wire from one side of potentiometer to Create ground (pin 5 on DB-9 connector).



Step 3: Assembly and Operation

Basic Operation

Preparation
1) Attach the 4th wheel to the back of the Create robot.
2) Construct the light sensor (see Step 1).
3) Attach the light sensor to one of the Command Module's serial ports (the center port is probably best, so the sensor stays near the robot's central body axis).
4) Download, compile, and load the program onto the Create Command Module (see Step 2).

Operation
1) Turn on the Command Module. The program will power up the robot, and start running the movement program automatically.
2) The basic algorithm is as follows:
Repeat
Sample the current sensory signal (light level)
If the signal is stronger, keep moving straight, otherwise turn to a random new orientation
Until user button pressed
3) The robot will back up and turn if an obstacle is bumped, and plays different songs depending on the action it takes (move straight, turning, backing up after a bump, ending the program).
4) To stop the program, press and hold the black user button (to the left of the red 'Reset' button) on the Command Module.

Step 4: Download the Source Code

The source code is based on the code example "light.c" that was provided with the AVR. The code has been modularized and separated into a driver file (simpletaxis.c) and two header files (drivefuns.h, accessory.h) for more ease of modification. Some of the original code from the example that is now in accessory.h is not used by the current program.

The source code follows, and is also attached as files.

Driver:

// Simple taxis program
// Written by Laura Grabowski
// 28 August 2007
//
// This program implements a biased random walk algorithm, using sensory input
// (currently from a photosensor) to track to the sensory source.
// -------------------------------------------------------------------------
// Based on code example provided with AVR, author not credited:
// light.c
// Designed to run on the Create module
//
// The basic architecture of this program can be re-used to easily
// write a wide variety of Create control programs. All sensor values
// are polled in the background (using the serial rx interrupt) and
// stored in the sensors array as long as the function
// delayAndUpdateSensors() is called periodically. Users can send commands
// directly a byte at a time using byteTx() or they can use the
// provided functions, such as baud() and drive().
// =========================================================================

#include "accessory.h"

// doBack moves the robot backward.
void doBack (uint8_t* backing_up) {
*backing_up = 0;
drive(-150, RadStraight);
delayAndUpdateSensors(DELAY_2SEC);
bumped = 0;
}

// doTurn executes a turn, either CW or CCW.
void doTurn (uint8_t* turn_dir, uint8_t* turning, uint16_t* turn_angle) {
if(*turn_dir == 0) {
if(angle > *turn_angle) *turning = 0;
drive(200, RadCCW);
}
else {
if((-angle) > *turn_angle) *turning = 0;
drive(200, RadCW);
}

// Use the current sensor value to select a random turn duration
delayAndUpdateSensors(random() & 0xBFF);
}

// doBump handles a bump by backing up and turning to a new random angle.
void doBump (uint8_t* backing_up, uint8_t* turn_dir, uint8_t* turning, uint16_t* turn_angle) {
// Play the alarm continuously
byteTx(CmdPlay);
byteTx(ALARM_SONG);

// Set the turn parameters and reset the angle
if(sensors[SenBumpDrop] & BumpLeft) *turn_dir = 0;
else *turn_dir = 1;

*backing_up = 1;
*turning = 1;
distance = 0;
angle = 0;
*turn_angle = randomAngle();

doBack(backing_up);
doTurn(turn_dir, turning, turn_angle);
}

// doForward moves the robot forward, polling for a bump.
void doForward(uint8_t* backing_up, uint8_t* turn_dir, uint8_t* turning, uint16_t* turn_angle) {
// Poll the sensors frequently to check for obstacles
for (int i = 0; i < 100; i++) {
delayAndUpdateSensors(DELAY_MIN);
if(!(sensors[SenBumpDrop] & BumpBoth)) drive(150, RadStraight);
else {
doBump(backing_up, turn_dir, turning, turn_angle);
bumped = 1;
break;
} // end else
} // end for
}

(2) Accessory functions:

// accessory.h
//
// Created by Laura Grabowski on 8/29/07.
// Assorted accessory functions for Create robot.
// -------------------------------------------------------------------------
The source code follows, and is also attached as files.

Driver:

// Simple taxis program
// Written by Laura Grabowski
// 28 August 2007
//
// This program implements a biased random walk algorithm, using sensory input
// (currently from a photosensor) to track to the sensory source.
// -------------------------------------------------------------------------
// Based on code example provided with AVR, author not credited:
// light.c
// Designed to run on the Create module
//
// The basic architecture of this program can be re-used to easily
// write a wide variety of Create control programs. All sensor values
// are polled in the background (using the serial rx interrupt) and
// stored in the sensors array as long as the function
// delayAndUpdateSensors() is called periodically. Users can send commands
// directly a byte at a time using byteTx() or they can use the
// provided functions, such as baud() and drive().
// =========================================================================

// Constants for songs and delay times
#define START_SONG 0
#define ALARM_SONG 1
#define MARCH_SONG 2
#define TURN_SONG 3
#define END_SONG 4
#define DELAY_MIN 20
#define DELAY_500MS 500
#define DELAY_750MS 750
#define DELAY_1SEC 1000
#define DELAY_2SEC 2000
#define DELAY_3SEC 3000

// Global variables
volatile uint16_t timer_cnt = 0;
volatile uint8_t timer_on = 0;
volatile uint8_t sensors_flag = 0;
volatile uint8_t sensors_index = 0;
volatile uint8_t sensors_in[Sen0Size];
volatile uint8_t sensors[Sen0Size];
volatile int16_t distance = 0;
volatile int16_t angle = 0;
volatile uint8_t bumped = 0; // @ LMG, a flag to set when a bump occurs

// Serial receive interrupt to store sensor values
SIGNAL(SIG_USART_RECV)
{
uint8_t temp;

temp = UDR0;

if(sensors_flag)
{
sensors_in[sensors_index++] = temp;
if(sensors_index >= Sen0Size)
sensors_flag = 0;
}
}

// Timer 1 interrupt to time delays in ms
SIGNAL(SIG_OUTPUT_COMPARE1A)
{
if(timer_cnt)
timer_cnt--;
else
timer_on = 0;
}

// Transmit a byte over the serial port
void byteTx(uint8_t value)
{
while(!(UCSR0A & _BV(UDRE0))) ;
UDR0 = value;
}

// Delay for the specified time in ms without updating sensor values
void delayMs(uint16_t time_ms)
{
timer_on = 1;
timer_cnt = time_ms;
while(timer_on) ;
}

// Delay for the specified time in ms and update sensor values
void delayAndUpdateSensors(uint16_t time_ms)
{
uint8_t temp;

timer_on = 1;
timer_cnt = time_ms;
while(timer_on)
{
if(!sensors_flag)
{
for(temp = 0; temp < Sen0Size; temp++)
sensors[temp] = sensors_in[temp];

// Update running totals of distance and angle
distance += (int)((sensors[SenDist1] << 8) | sensors[SenDist0]);
angle += (int)((sensors[SenAng1] << 8) | sensors[SenAng0]);

byteTx(CmdSensors);
byteTx(0);
sensors_index = 0;
sensors_flag = 1;
}
}
}

// Initialize the Mind Control's ATmega168 microcontroller
void initialize(void)
{
cli();

// Set I/O pins
DDRB = 0x10;
PORTB = 0xCF;
DDRC = 0x00;
PORTC = 0xFF;
DDRD = 0xE6;
PORTD = 0x7D;

// Set up timer 1 to generate an interrupt every 1 ms
TCCR1A = 0x00;
TCCR1B = (_BV(WGM12) | _BV(CS12));
OCR1A = 71;
TIMSK1 = _BV(OCIE1A);

// Set up the serial port with rx interrupt
UBRR0 = 19;
UCSR0B = (_BV(RXCIE0) | _BV(TXEN0) | _BV(RXEN0));
UCSR0C = (_BV(UCSZ00) | _BV(UCSZ01));

// Set up the ADC on pin C5
DIDR0 |= 0x20; // disable digital input on C5
PRR &= ~_BV(PRADC); // Turn off ADC power save
ADCSRA = (_BV(ADEN) | _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0)); // Enabled, prescaler = 128
ADMUX = (_BV(REFS0) | 0x05); // set voltage reference, select channel C5

// Turn on interrupts
sei();
}

void powerOnRobot(void)
{
// If Create's power is off, turn it on
if(!RobotIsOn)
{
while(!RobotIsOn)
{
RobotPwrToggleLow;
delayMs(500); // Delay in this state
RobotPwrToggleHigh; // Low to high transition to toggle power
delayMs(100); // Delay in this state
RobotPwrToggleLow;
}
delayMs(3500); // Delay for startup
}
}

// Switch the baud rate on both Create and module
void baud(uint8_t baud_code)
{
if(baud_code <= 11)
{
byteTx(CmdBaud);
UCSR0A |= _BV(TXC0);
byteTx(baud_code);
// Wait until transmit is complete
while(!(UCSR0A & _BV(TXC0))) ;

cli();

// Switch the baud rate register
if(baud_code == Baud115200)
UBRR0 = Ubrr115200;
else if(baud_code == Baud57600)
UBRR0 = Ubrr57600;
else if(baud_code == Baud38400)
UBRR0 = Ubrr38400;
else if(baud_code == Baud28800)
UBRR0 = Ubrr28800;
else if(baud_code == Baud19200)
UBRR0 = Ubrr19200;
else if(baud_code == Baud14400)
UBRR0 = Ubrr14400;
else if(baud_code == Baud9600)
UBRR0 = Ubrr9600;
else if(baud_code == Baud4800)
UBRR0 = Ubrr4800;
else if(baud_code == Baud2400)
UBRR0 = Ubrr2400;
else if(baud_code == Baud1200)
UBRR0 = Ubrr1200;
else if(baud_code == Baud600)
UBRR0 = Ubrr600;
else if(baud_code == Baud300)
UBRR0 = Ubrr300;

sei();

delayMs(100);
}
}

// Send Create drive commands in terms of velocity and radius
void drive(int16_t velocity, int16_t radius)
{
byteTx(CmdDrive);
byteTx((uint8_t)((velocity >> 8) & 0x00FF));
byteTx((uint8_t)(velocity & 0x00FF));
byteTx((uint8_t)((radius >> 8) & 0x00FF));
byteTx((uint8_t)(radius & 0x00FF));
}

// Return an angle value in the range 53 to 180 (degrees)
uint16_t randomAngle(void)
{
return (53 + ((uint16_t)(random() & 0xFF) >> 1));
}

// Define songs to be played later
void defineSongs(void)
{
// Start song
byteTx(CmdSong);
byteTx(START_SONG);
byteTx(5);
byteTx(60);
byteTx(24);
byteTx(79);
byteTx(24);
byteTx(75);
byteTx(24);
byteTx(72);
byteTx(24);
byteTx(76);
byteTx(48);

// Alarm song
byteTx(CmdSong);
byteTx(ALARM_SONG);
byteTx(7);
byteTx(86);
byteTx(24);
byteTx(83);
byteTx(24);
byteTx(86);
byteTx(24);
byteTx(83);
byteTx(24);
byteTx(86);
byteTx(24);
byteTx(83);
byteTx(24);
byteTx(0);
byteTx(96);

// March @ LMG
// This is from Sousa's "Liberty Bell March," which is in the public domain
byteTx(CmdSong);
byteTx(MARCH_SONG);
byteTx(15);
byteTx(89);
byteTx(8);
byteTx(86);
byteTx(24);
byteTx(86);
byteTx(8);
byteTx(86);
byteTx(8);
byteTx(85);
byteTx(8);
byteTx(86);
byteTx(8);
byteTx(94);
byteTx(24);
byteTx(89);
byteTx(8);
byteTx(89);
byteTx(24);
byteTx(86);
byteTx(8);
byteTx(87);
byteTx(24);
byteTx(87);
byteTx(8);
byteTx(87);
byteTx(24);
byteTx(89);
byteTx(8);
byteTx(91);
byteTx(24);

// Turn song @ LMG
byteTx(CmdSong);
byteTx(TURN_SONG);
byteTx(2);
byteTx(65);
byteTx(24);
byteTx(59);
byteTx(24);

// End song @ LMG
// Taps, also public domain
byteTx(CmdSong);
byteTx(END_SONG);
byteTx(6);
byteTx(48);
byteTx(24);
byteTx(48);
byteTx(8);
byteTx(53);
byteTx(96);
byteTx(48);
byteTx(24);
byteTx(53);
byteTx(8);
byteTx(57);
byteTx(96);
}