New sensor for RoboBall

From RoboWiki
Jump to: navigation, search

This is a simple project you can build on your own. It allows you to detect RoboCup Junior Soccer RoboBall. You need:

  • 1 AVR ATtiny26L processor (unless you are an expert, use the PDIP package)
  • 11 phototransistors (we used L-53P3BT, which is L-53P3C with blue transparent lens)
  • 11 resistors (we used 10K Ohm)
  • optionally: 1 LED and 1K Ohm resistor (to indicate the operation of the sensor)
  • some testing board which you can use to build your sensor
  • soldering and wiring
  • 5V power supply (e.g. 4x rechargable AA batteries)
  • programmer for AVR (for instance AVR Dragon)

New roboball sensor.gif

Connect each of the phototransistors to one ADC input of the AVR and to the ground, connect each of the 10K resistors between VCC and the ADC input of the AVR. Connect LED to pin1 of CPU through 1K resistor and to ground. Connect the pins 2,3, and 4 to the other CPU (for example ATmega128) through a short communicating cable (using shielded cable is an advantage).

The sensor is a low-cost solution, the price of one phototransistor is about 0.25 EUR, ATtiny26L is for about 3.7 EUR, making the total price safely under 10 EUR.

Then download the following code to the sensor CPU:

/* Intelligent sensor for RoboSoccer Ball detection
 * 
 *   Implemented for AVR ATtiny26L - a low-cost CPU with 11 A2D converters
 *   The sensor communicates using three digital lines with the main CPU.
 *   The idea is that the 11 IR sensors are mounted around the robot.
 *   On request, the sensor sends the location of the ball (0-22) based
 *    on the readings from the 11 IR sensors (we used the L-53P3BT phototransistors
 *    and 10K resistors in voltage divider configuration).
 *    CPU is powered with 5V.
 *   
 *   Compiled with AVR Studio 4.16, WINAVR 20090313. You need to link this
 *   together with a2d.c from AvrLib.
 *
 *   Author: Palo, ppetrovic@acm.org, April 2009
 */ 

#include <inttypes.h>
#include <avr/io.h>
#include <util/delay.h>

// the following is included from Procyon AVRLib 
// (http://hubbard.engr.scu.edu/embedded/avr/avrlib/)
#include <a2d.h>

uint16_t val[33];    // three readings from the sensors
uint16_t val2[14];   // averaged readings from the sensors
uint16_t max, d0, d1, maxd1;   // finding the maximum readings
uint8_t dir, d;		 // direction index

// three communication lines on the side of the slave (attiny26): PORTB 1-3
#define PDREADY PORTB
#define PDATALINE PORTB
#define PDATAREAD PINB
#define BDREADY 2
#define BDATALINE 4
#define BDATAREAD 8

// sends a 16-bit number to the main CPU
uint8_t send_ir(uint16_t x)
{
  uint8_t i;
  uint32_t cnt;
  // if the master does not request data, do not send
  if (!(PDATAREAD & BDATAREAD)) return 0;
  // indicate that we are ready to send
  PDATALINE |= BDATALINE;
  cnt = 0;
  // wait for master to indicate that it is ready to receive
  while ((PDATAREAD & BDATAREAD) && (cnt < 1000000)) { PDATALINE |= BDATALINE; cnt++;}
  if (cnt == 1000000) return 0; // communication lost -> sending failed
  // bit-by-bit
  for (i = 0; i < 16; i++)
  {
    // send the next bit
    uint8_t tmp = PDATALINE & (~BDATALINE);
    PDATALINE = tmp | (x & 1) * BDATALINE;
	// indicate that the data is online
    PDREADY |= BDREADY;
	// wait for master to read the data
	cnt = 0;
	while ((!(PDATAREAD & BDATAREAD)) && (cnt < 1000000)) { PDREADY |= BDREADY; cnt++; }
	if (cnt == 1000000) return 0;
    // getting ready for more data
	PDREADY &= ~BDREADY;
	// wait until master gets ready as well
	cnt = 0;
	while ((PDATAREAD & BDATAREAD) && (cnt < 1000000)) { PDREADY &= ~BDREADY; cnt++; }
	if (cnt == 1000000) return 0;
	// prepare next data
	x >>= 1;
  }
  // clear the data line
  PDATALINE &= ~BDATALINE;
  return 1;
}

// sensor main function
int main()
{

	int i;
	uint8_t sent, trial;

	DDRB = 1 + BDREADY + BDATALINE;
	PORTB = 1;  // there is a LED on pin0 of PORTB

	DDRA = 0;   // clean the ADC port
	PORTA = 0;  // disconnect pull-ups

    // setup A2D converter
    a2dInit();
    a2dSetPrescaler(ADC_PRESCALE_DIV128); // given 8MHz CPU clock, this gives good sampling precision
	a2dSetReference(0);  // OUCH! ATtiny26 has some of the ADMUX flags reveresed!

    // start-up LED flashing
    for (i = 0; i < 10; i++) _delay_ms(10);
	PORTB = 0;
    for (i = 0; i < 10; i++) _delay_ms(10);
    PORTB = 1;

    // sensors just sends the data forever
	while (1)
	{
      // three-times sample all the IR transistors
	  for (trial = 0; trial < 3; trial++)
        for (dir = 0; dir < 11; dir++)
  	      val[trial * 11 + dir] = 1023 - a2dConvert10bit(dir);

      // compute average readings
      for (dir = 0; dir < 11; dir++)
	    val2[dir + 1] = (val[dir] + val[dir + 11] + val[dir + 11]) / 3;
      // sensors are on a circle, so we wrap with the first and the last 
	  val2[0] = val2[11];
	  val2[12] = val2[1];

      // consider 23 different directions: 11 in the line of sensors, and 12 between sensors
	  max = 0; d = 0; maxd1 = 0;
	  for (dir = 0; dir < 23; dir++)
	  {
	    // if the direction in the line of sensor scaled 90% is more than averege of two, it wins
	    if (dir & 1) { d1 = val2[1 + (dir >> 1)]; d0 = (d1 * (uint16_t) 9) / (uint16_t) 10; } 
		// otherwise take a direction between two neighboring sensors
		else { d0 = (val2[dir >> 1] + val2[1 + (dir >> 1)]) >> 1; d1 = d0;}
		// find the direction with maximum value
		if (d0 > max) { max = d0; maxd1 = d1; d = dir; }
      }

      // send the data (direction + value) to the master
      do { sent = send_ir(2000+d); } while (!sent);
      do { sent = send_ir(maxd1); } while (!sent);

      // LED flashes when master reads data
	  PORTB ^= 1;
    }

	return 0;
}


On the side of the master, use analogous program.


// in this case, the communication line is connected to pins 3,4,5 on PORTC
#define PDATALINE PINC
#define PDATAREAD PORTC
#define PDREADY PINC

#define BDREADY 8
#define BDATALINE 16
#define BDATAREAD 32

/* read a single 16-bit unsigned integer from sensor CPU over the 3-wire communication line */
uint16_t read_ir()
{
  uint8_t i;
  uint16_t x = 0;
  uint32_t cnt; 

  // indicate that we are ready to read  
  PDATAREAD |= BDATAREAD;
  // wait for the slave to get ready to send
  cnt = 0;
  while ((!(PDATALINE & BDATALINE)) && (cnt < 1000000)) { PDATAREAD |= BDATAREAD; cnt++; }   //TODO: check for timeout
  if (cnt == 1000000) return 65535; // when the connection is lost, receiving failed

  // request the first bit of data
  PDATAREAD &= ~BDATAREAD;
  for (i = 0; i < 16; i++)
  {
    // wait until next bit is online
    cnt = 0;
    while ((!(PDREADY & BDREADY)) && (cnt < 1000000)) { PDATAREAD &= ~BDATAREAD; cnt++; }
	if (cnt == 1000000) return 65535;
	// read the next bit
	x += ((PDATALINE & BDATALINE) / BDATALINE) << i;
	// indicate that the data was read
	PDATAREAD |= BDATAREAD;
	// wait for the slave to learn that the data was read
	cnt = 0;
	while ((PDREADY & BDREADY) && (cnt < 1000000)) { PDATAREAD |= BDATAREAD; cnt++; }
	// request next bit of data
	PDATAREAD &= ~BDATAREAD;
  } 

  // return the number read.
  return x;
}

. . .

{ 
  uint16_t n;
  printf("rcv:");
  while (!terminate)   
  { 
    n = read_ir();   // read value from the sensor and print it out
    if (n > 2000) printf("%2d:", n - 2000); // direction
    else printf("%4d\n", n);                // distance
  }
}

The picture shows our prototyping board before the sensor was built:

New roboball sensor.jpg

Remember to connect the GND of both CPUs.