Introduction: How to Make a Robo-Bellhop

About: I like taking things apart and figuring out how they work. I generally loose interest after that.

This Instructable shows how to use the iRobot Create to make a moving bellhop. This was lifted entirely with permission from carolDancer's instructions, and I put it up as a sample entry for our contest.

Robo-BellHop can be your own personal assistant to carry your bags, groceries, laundry, etc., so you don't have to. The basic Create has a bin attached to the top and uses two on-board IR detectors to follow its owner's IR transmitter. With very basic C software code, the user can secure heavy groceries, a large load of laundry, or your overnight bag onto Robo-BellHop and have the robot follow you down the street, through the mall, down the hall or through the airport -- wherever the user needs to go.



Basic Operation
1) Hit the Reset button to turn on command module and check sensors are engaging
1a) the Play LED should go on when it sees the IR transmitter to follow you
1b) the Advance LED should go on when the robot is at very close range
2) Hit black soft button to run Robo-BellHop routine
3) Attach IR transmitter to ankle and make sure it's turned on. Then load up the basket and go!
4) The logic of Robo-BellHop is as follows:
4a) As you walk around, if the IR signal is being detected, the robot will drive at max speed
4b) If the IR signal goes out of range (by being too far or too sharp an angle), the robot will traverse a short distance at slow speed in case the signal is picked up again
4c) If the IR signal is not being detected, the robot will turn left and right in an attempt to find the signal again
4d) If the IR signal is being detected but the robot hits an obstacle, the robot will attempt to drive around the obstacle
4e) If the robot gets very close to the IR signal, the robot will stop to avoid hitting the owner's ankles

Hardware
1 iRobot virtual wall unit - $30
1 IR detector from RadioShack - $3
1 DB-9 male connector from Radio Shack - $4
4 6-32 screws from Home Depot - $2.50
2 3V batteries, I used D
1 laundry basket from Target - $5
1 extra wheel to onto the back of the Create robot
Electrical tape, wire and solder

Step 1: Covering the IR Sensor

Attach electrical tape to cover all but a small slit of the IR sensor on the front of the Create robot. Dismantle the virtual wall unit and extract the small circuit board at the front of the unit. This is a bit tricky because there are lots of hidden screws and plastic mounts. The IR transmitter is on the circuit board. Cover the IR transmitter with a piece of tissue paper to avoid IR reflections. Attach the circuit board to a strap or elastic band that can wrap around your ankle. Wire up the batteries to the circuit board so that you can have the batteries in a comfortable place (I made it so that I could put the batteries in my pocket).

Wire up the 2nd IR detector to the DB-9 connector and insert into Cargo Bay ePort pin 3 (signal) and pin 5 (ground). Attach the 2nd IR detector to the top of the existing IR sensor on Create and cover it with a couple of layers of tissue paper until the 2nd IR detector doesn't see the emitter at a distance that you want the Create robot to stop to keep from hitting you. You can test this after you've hit the Reset button and watch the Advance LED to go on when you're at the stop distance.

Step 2: Attach the Basket

Attach the basket using the 6-32 screws. I just mounted the basket to the top of the Create robot. Also slide in the back wheel so you place weight on the back of the Create robot.

Notes:
- The robot can carry quite a bit of load, at least 30 lbs.
- The small size seemed to be the hardest part about getting it to carry any luggage
- IR is very temperamental. Perhaps using imaging is better but it's much more expensive

Step 3: Download the Source Code.

The source code follows, and is attached in a text file:

/********************************************************************* follow.c ** -------- ** runs on Create Command Module ** cover all but small opening on the front of the IR sensor ** Create will follow a virtual wall (or any IR sending out the ** force-field signal) and hopefully avoid obstacles in the process *********************************************************************/#include interrupt.h>#include io.h>#include#include "oi.h"#define TRUE 1#define FALSE 0#define FullSpeed 0x7FFF#define SlowSpeed 0x0100#define SearchSpeed 0x0100#define ExtraAngle 10#define SearchLeftAngle 125#define SearchRightAngle (SearchLeftAngle - 1000)#define CoastDistance 150#define TraceDistance 250#define TraceAngle 30#define BackDistance 25#define IRDetected (~PINB & 0x01)//states#define Ready 0#define Following 1#define WasFollowing 2#define SearchingLeft 3#define SearchingRight 4#define TracingLeft 5#define TracingRight 6#define BackingTraceLeft 7#define BackingTraceRight 8// Global variablesvolatile 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[Sen6Size];volatile uint8_t sensors[Sen6Size];volatile int16_t distance = 0;volatile int16_t angle = 0;volatile uint8_t inRange = 0;// Functionsvoid byteTx(uint8_t value);void delayMs(uint16_t time_ms);void delayAndCheckIR(uint16_t time_ms);void delayAndUpdateSensors(unsigned int time_ms);void initialize(void);void powerOnRobot(void);void baud(uint8_t baud_code);void drive(int16_t velocity, int16_t radius);uint16_t randomAngle(void);void defineSongs(void);int main (void){//state variableuint8_t state = Ready;int found = 0;int wait_counter = 0;// Set up Create and moduleinitialize();LEDBothOff;powerOnRobot();byteTx(CmdStart);baud(Baud28800);byteTx(CmdControl);byteTx(CmdFull);// set i/o for second IR sensorDDRB &= ~0x01; //set cargo bay ePort pin 3 to be an inputPORTB |= 0x01; //set cargo ePort pin3 pullup enabled// program loopwhile(TRUE){// Stop just as a precautiondrive(0, RadStraight);// set LEDsbyteTx(CmdLeds);byteTx(((sensors[SenVWall])?LEDPlay:0x00) | (inRange?LEDAdvance:0x00));byteTx(sensors[SenCharge1]);byteTx(64);IRDetected?LED2On:LED2Off;inRange?LED1On:LED1Off;//looking for user button, check oftendelayAndUpdateSensors(10);delayAndCheckIR(10);if(UserButtonPressed) {delayAndUpdateSensors(1000);//active loopwhile(!(UserButtonPressed)&&(!sensors[SenCliffL])&&(!sensors[SenCliffFL])&&(!sensors[SenCliffFR])&&(!sensors[SenCliffR])) {byteTx(CmdLeds);byteTx(((sensors[SenVWall])?LEDPlay:0x00) | (inRange?LEDAdvance:0x00));byteTx(sensors[SenCharge1]);byteTx(255);IRDetected?LED2On:LED2Off;inRange?LED1On:LED1Off;switch(state) {case Ready:if(sensors[SenVWall]) {//check for proximity to leaderif(inRange) {drive(0,RadStraight);} else {//drive straightdrive(SlowSpeed, RadStraight);state = Following;}} else {//search for the beamangle = 0;distance = 0;wait_counter = 0;found = FALSE;drive(SearchSpeed, RadCCW);state = SearchingLeft;}break;case Following:if(sensors[SenBumpDrop] & BumpRight) {distance = 0;angle = 0;drive(-SlowSpeed, RadStraight);state=BackingTraceLeft;} else if(sensors[SenBumpDrop] & BumpLeft) {distance = 0;angle = 0;drive(-SlowSpeed, RadStraight);state=BackingTraceRight;} else if(sensors[SenVWall]) {//check for proximity to leaderif(inRange) {drive(0,RadStraight);state = Ready;} else {//drive straightdrive(FullSpeed, RadStraight);state = Following;}} else {//just lost the signal, keep going slowly one cycledistance = 0;drive(SlowSpeed, RadStraight);state = WasFollowing;}break;case WasFollowing:if(sensors[SenBumpDrop] & BumpRight) {distance = 0;angle = 0;drive(-SlowSpeed, RadStraight);state=BackingTraceLeft;} else if(sensors[SenBumpDrop] & BumpLeft) {distance = 0;angle = 0;drive(-SlowSpeed, RadStraight);state=BackingTraceRight;} else if (sensors[SenVWall]) {//check for proximity to leaderif(inRange) {drive(0,RadStraight);state = Ready;} else {//drive straightdrive(FullSpeed, RadStraight);state = Following;}} else if (distance >= CoastDistance) {drive(0,RadStraight);state = Ready;} else {drive(SlowSpeed, RadStraight);}break;case SearchingLeft:if(found) {if (angle >= ExtraAngle) {drive(SlowSpeed, RadStraight);state = Following;} else {drive(SearchSpeed, RadCCW);}} else if (sensors[SenVWall]) {found = TRUE;angle = 0;if (inRange) {drive(0,RadStraight);state = Ready;} else {drive(SearchSpeed, RadCCW);}} else if (angle >= SearchLeftAngle) {drive(SearchSpeed, RadCW);wait_counter = 0;state = SearchingRight;} else {drive(SearchSpeed, RadCCW);}break;case SearchingRight:if(found) {if (-angle >= ExtraAngle) {drive(SlowSpeed, RadStraight);state = Following;} else {drive(SearchSpeed, RadCW);}} else if (sensors[SenVWall]) {found = TRUE;angle = 0;if (inRange) {drive(0,RadStraight);state = Ready;} else {drive(SearchSpeed, RadCCW);}} else if(wait_counter > 0) {wait_counter -= 20;drive(0,RadStraight);} else if (angle = SearchRightAngle) {drive(0,RadStraight);wait_counter = 5000;angle = 0;} else {drive(SearchSpeed, RadCW);}break;case TracingLeft:if(sensors[SenBumpDrop] & BumpRight) {distance = 0;angle = 0;drive(-SlowSpeed, RadStraight);state=BackingTraceLeft;} else if(sensors[SenBumpDrop] & BumpLeft) {drive(0, RadStraight);state=Ready;} else if (sensors[SenVWall]) {//check for proximity to leaderif(inRange) {drive(0,RadStraight);state = Ready;} else {//drive straightdrive(SlowSpeed, RadStraight);state = Following;}} else if (!(distance >= TraceDistance)) {drive(SlowSpeed, RadStraight);} else if (!(-angle >= TraceAngle)) {drive(SearchSpeed, RadCW);} else {distance = 0;angle = 0;drive(SlowSpeed, RadStraight);state = Ready;}break;case TracingRight:if(sensors[SenBumpDrop] & BumpRight) {drive(0, RadStraight);state=Ready;} else if(sensors[SenBumpDrop] & BumpLeft) {distance = 0;angle = 0;drive(-SlowSpeed, RadStraight);state=BackingTraceRight;} else if (sensors[SenVWall]) {//check for proximity to leaderif(inRange) {drive(0,RadStraight);state = Ready;} else {//drive straightdrive(SlowSpeed, RadStraight);state = Following;}} else if (!(distance >= TraceDistance)) {drive(SlowSpeed, RadStraight);} else if (!(angle >= TraceAngle)) {drive(SearchSpeed, RadCCW);} else {distance = 0;angle = 0;drive(SlowSpeed, RadStraight);state = Ready;}break;case BackingTraceLeft:if (sensors[SenVWall] && inRange) {drive(0,RadStraight);state = Ready;} else if (angle >=TraceAngle) {distance = 0;angle = 0;drive(SlowSpeed, RadStraight);state = TracingLeft;} else if (-distance >= BackDistance) {drive (SearchSpeed, RadCCW);} else {drive(-SlowSpeed, RadStraight);}break;case BackingTraceRight:if (sensors[SenVWall] && inRange) {drive(0,RadStraight);state = Ready;} else if (-angle >=TraceAngle) {distance = 0;angle = 0;drive(SlowSpeed, RadStraight);state = TracingRight;} else if (-distance >= BackDistance) {drive (SearchSpeed, RadCW);} else {drive(-SlowSpeed, RadStraight);}break;default://stopdrive(0,RadStraight);state = Ready;break;}delayAndCheckIR(10);delayAndUpdateSensors(10);}//cliff or userbutton detected, allow condition to stabilize (eg, button to be released)drive(0,RadStraight);delayAndUpdateSensors(2000);}}}// Serial receive interrupt to store sensor valuesSIGNAL(SIG_USART_RECV){uint8_t temp;temp = UDR0;if(sensors_flag){sensors_in[sensors_index++] = temp;if(sensors_index >= Sen6Size)sensors_flag = 0;}}// Timer 1 interrupt to time delays in msSIGNAL(SIG_OUTPUT_COMPARE1A){if(timer_cnt)timer_cnt--;elsetimer_on = 0;}// Transmit a byte over the serial portvoid byteTx(uint8_t value){while(!(UCSR0A & _BV(UDRE0))) ;UDR0 = value;}// Delay for the specified time in ms without updating sensor valuesvoid delayMs(uint16_t time_ms){timer_on = 1;timer_cnt = time_ms;while(timer_on) ;}// Delay for the specified time in ms and check second IR detectorvoid delayAndCheckIR(uint16_t time_ms){uint8_t timer_val = 0;inRange = 0;timer_on = 1;timer_cnt = time_ms;while(timer_on) {if(!(timer_val == timer_cnt)) {inRange += IRDetected;timer_val = timer_cnt;}}inRange = (inRange>=(time_ms>>1));}// Delay for the specified time in ms and update sensor valuesvoid 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 Sen6Size; temp++)sensors[temp] = sensors_in[temp];// Update running totals of distance and angledistance += (int)((sensors[SenDist1] 8) | sensors[SenDist0]);angle += (int)((sensors[SenAng1] 8) | sensors[SenAng0]);byteTx(CmdSensors);byteTx(6);sensors_index = 0;sensors_flag = 1;}}}// Initialize the Mind Control's ATmega168 microcontrollervoid initialize(void){cli();// Set I/O pinsDDRB = 0x10;PORTB = 0xCF;DDRC = 0x00;PORTC = 0xFF;DDRD = 0xE6;PORTD = 0x7D;// Set up timer 1 to generate an interrupt every 1 msTCCR1A = 0x00;TCCR1B = (_BV(WGM12) | _BV(CS12));OCR1A = 71;TIMSK1 = _BV(OCIE1A);// Set up the serial port with rx interruptUBRR0 = 19;UCSR0B = (_BV(RXCIE0) | _BV(TXEN0) | _BV(RXEN0));UCSR0C = (_BV(UCSZ00) | _BV(UCSZ01));// Turn on interruptssei();}void powerOnRobot(void){// If Create's power is off, turn it onif(!RobotIsOn){while(!RobotIsOn){RobotPwrToggleLow;delayMs(500); // Delay in this stateRobotPwrToggleHigh; // Low to high transition to toggle powerdelayMs(100); // Delay in this stateRobotPwrToggleLow;}delayMs(3500); // Delay for startup}}// Switch the baud rate on both Create and modulevoid baud(uint8_t baud_code){if(baud_code = 11){byteTx(CmdBaud);UCSR0A |= _BV(TXC0);byteTx(baud_code);// Wait until transmit is completewhile(!(UCSR0A & _BV(TXC0))) ;cli();// Switch the baud rate registerif(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 radiusvoid 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));}