How to build an Omni Wheels Robot

By on May 8, 2013

The invention of the wheel, literally revolutionized the history of man. Thanks to it, it was possible to create forms of transportation, invent new industrial and architectural machinery. The wheel is so perfect that no one would think to improve it but recently some special, revolutionary wheels have been invented. These wheels are called “omnidirectional” and essentially consist of a set of normal wheels assembled in such a way as to allow new kind of movements.

omniwheels

With omnidirectional wheels is possible to create moving platforms with only three or four wheels, with evident savings respect to the version seen before. However, using wheels of this kind you lose the ability to exploit full motor torque and, in the case of rough surfaces, these are unable to guarantee an exact forward direction and, for this reason, have not been used often on a commercial scale.

Omnidirectional wheels can be used to create simple three wheels platforms and small robots, such as those competing in the RoboCup.

There is a third possibility to create omnidirectional platforms: use recently invented wheels called Swerve Crab Drive. In addition to the classical rotation, these wheels have the possibility to revolve around their diameters. To avoid the motor wires to be intertwined, the traction mechanism is conveyed through gears through the fixing pin. A second mechanism allows the wheel to turn on itself: a total 5 to 8 motors is needed (for four wheels platforms), depending on whether all the wheels can steer or only some of them.

Such a solution implies not negligible costs, however, the freedom of movement that you end up having is quite significant since each wheel rotates according to the direction of the movement. Each wheel is therefore able to transfer all the engine power to the ground. The platform is also insensitive to uneven terrains and you’ve great maneuverability and accuracy.

Our OmniWheels robot

omniwheels2

After having studied the functionalities of these type of wheel we ended up creating a small robotic platform using three omniwheels, arranged with a 120° angle between them. The goal was to have a cost-effective, easy to assemble, platform based on Arduino, reusing components coming from other projects.

omniwheels4

Omniwheels available on the market are usually very expensive, therefore we preferred to use 48 mm diameter wheels that are available in our store: these are half-wheels because, to achieve a complete omni-directional wheel, you need to assemble two of these wheels with a 60° offset. To achieve our six-wheeled platform we therefore need six of these. For the motors, we used two geared motors, controlled with two motorshields.

motorshieldRead the Motorshield article

We picked the motorshield since it gives us the ability to configure the pins to control the power drivers and, in this way, with two shields, you can control a total of four engines.

Table 1 – Pin assignment to motorshields

shield pin Arduino connection Motor
1 PWMA 3 A
1 DIRA 2 A
1 PWMB 9 B
1 DIRB 8 B
2 PWMA 5 Not used
2 DIRA 4 Not used
2 PWMB 10 C
2 DIRB 12 C

It is essential to follow strictly what’s in the Table, because all the three engines must have the same power supply: in our case, a PWM signal at the same frequency.

Table 2 shows the registers and timers used, by the analogWrite function which allows you to set the PWM outputs. The PWM signal headed to pins 5 and 6 uses the Timer0 register but generates a PWM signal with twice the frequency of the other, causing light disparities in the engines operation.

Table 2 – Registers associated with the Arduino PWM.

pin function shield Timer register PWM frequency
3 PWMA 1

2

OC2B

488,28
5 PWMC 2

0

OC0B

976.56
6 Not used  

0

OC0A

976.56
9 PWMB 1

1

OC1A

488,28
10 PWMD 2

1

OC1B

488,28
11 Not used  

2

OC2A

488,28

It is vital that the direction of the rotation of the motors is homogeneous: to allow the motors to turn in the same direction, the bit DIRA, DIRB, DIRC of the motors drivers must have the same logic setting. For example, when these bits are set to logic zero wheels shall rotate clockwise.

omniwheels3

Wiring is very simple: for the electronics you just need to stack the shield on on top of another on the Arduino, then it is sufficient to wire the engine by connecting the wires to the appropriate terminals of the shield.

Power is supplied by a two cells LiPo battery connected directly to the Arduino power plug, whose voltage, even when the battery is fully charged (over 8V), does not create problems, due to the voltage drop due to the Arduino internal diode and due to power losses on the drivers.

To complete the hardware you need to install an PNA4602/IR38DM IR sensor that will be used as a receiver for a cheap remote control based on infrared technology.

You just need to connect the sensor power to the +5 V and GND pins of the Arduino board and the output must be connected to the 6 digital pin. To filter out possible interference from the motors we soldered a small 100 nF capacitor directly on the power pin of the IR sensor. To attach the wheels to the axis of the motors you need to tinker a little, since the shaft of the geared motors has a smaller diameter than that of the wheels: you need an adapter or a bit of (good) glue.

omniwheels_comadn

For the remote control we designed a cheap but very functional solution, which consists in recycling the infrared remote control of a toy helicopter: this solution is ideal as you can get such stuff spending peanuts: it is not easy to fly these helicopters so it is very likely that someone ended up with a spare remote control :)

There are different remote controls on the market, but all share the technology : serial transmission via modulation of infrared rays. Our remote was from a small three channels helicopter (commercial value of 20 Euros).

All we had to do was to discover and reconstruct the codes used to receive the optical signals and convert them into electrical impulses. Doing this, took a very simple 38 kHz PNA4602/IR38DM sensor, that can be easily connected to the Arduino. From the web page https://github.com/adafruit/Raw-IR-decoder-for-Arduino we downloaded the sketch named Raw-IR-decoder-for-Arduino that allows the analysis of the bits coming from any serial transmission, regardless of the speed and the type of bits sent.

After having successfully extrapolated the remote control signals and processed the bit sequence you obtain the values ​​for each channel.

 

byte name function Value at rest Increase if you move the stick to the Decrease if stick shifted to
0 yaw turn

63

left

right

1 pitch foward

63

back

front

2 throttle power

0

front

back

3 trim trimmer

left

right

The protocol used in the IR remote control is as follows:

S 0YYYYYYY 0PPPPPPP CTTTTTTT 0RRRRRRR AND

S – Start Symbol

Y – 7-bit yaw

P – 7-bit pitch

C – 1-bit channel – 0 channel A, 1 channel B

T – 7-bit throttle

R – 7-bit trimmer

E – End Symbol

Variable names refer to the functions carried out by the helicopter, the term YAW indicates the rotation on itself, the term PITCH indicates the advancement and THROTTLE the ascent and descent.

In our application, however, we use the functions in a slightly different way: the right stick consists of two channels and provides indication about the direction and speed of the platform while the trimmer will be used to set the rotation on itself.

Now we just need to write the Arduino firmware which operates the platform, after having understood how to control the omnidirectional wheels. In usual applications operating engines is quite intuitive: being two or four wheels, the operation principle is the very same. Just think of a crawler robot, if the wheels turn in the same direction the robot moves on, if they rotate in opposite directions the robot turns on itself: using omnidirectional wheels all this is not so intuitive, even less if you use a three wheels structure instead of four wheels one. To solve the puzzle you need to work on a bit of math, specifically vectors, and think about how the wheels behave when set into rotation.

 Sketch

// OmniWheels
//open-electronics.org
//by Mirco Segatello

#define IRpin 6	             // pin for IR sensor
#define SIGNAL_SIZE 32 	     // number bit of sequence
#define SIGNAL_START 1600    // width bit start
#define SIGNAL_0_min 150     // width bit 0 min
#define SIGNAL_0_max 250     // width bit 0 max
#define SIGNAL_1_min 450     // width bit 1 min
#define SIGNAL_1_max 650     // width bit i max
unsigned int SignalWidth[SIGNAL_SIZE];
byte yaw, pitch, throttle, trim;

#define DIRA_pin 2
#define PWMA_pin 3
#define DIRB_pin 8
#define PWMB_pin 9
#define DIRC_pin 12
#define PWMC_pin 10
#define DIRD_pin 4
#define PWMD_pin 5

float motA, motB, motC;
int PWMA, PWMB, PWMC;

int i;


void setup()
{
  Serial.begin(9600);
  pinMode(IRpin, INPUT);
  pinMode(DIRA_pin, OUTPUT);    
  pinMode(DIRB_pin, OUTPUT);  
  pinMode(DIRC_pin, OUTPUT); 
  analogWrite(PWMA, 0); 
  analogWrite(PWMB, 0);
  analogWrite(PWMC, 0);
  Serial.println("Wait IR Signal...");
}

void loop()
{
  if(IR_Receive()==0)
  {
    Serial.print("yaw=");
    Serial.print(yaw, DEC);
    Serial.print ("\tpitch=");
    Serial.print(pitch, DEC);
    Serial.print ("\tthrottle=");    
    Serial.print(throttle, DEC);
    Serial.print ("\ttrim=");    
    Serial.print(trim, DEC);
    Serial.println();    
    SetMotor();
    delay(200);
  }
}

void SetMotor()
{

  motA = 4.0 * (-0.5* float(63-yaw)-0.866*float(63-pitch)) + float(63-trim);
  motB = 4.0 * (-0.5* float(63-yaw)+0.866*float(63-pitch)) + float(63-trim);
  motC = 4.0 * (float(63-yaw)) + float(63-trim);

  PWMA = int(constrain(motA,-255,255));
  PWMB = int(constrain(motB,-255,255));
  PWMC = int(constrain(motC,-255,255));  
  
  Serial.print("PWMA=");
  Serial.print(PWMA);
  Serial.print("\tPWMB=");
  Serial.print(PWMB);
  Serial.print("\tPWMC=");
  Serial.println(PWMC);
  
  Serial.println();

  if (PWMA < 0)
  {
     analogWrite(PWMA_pin, abs(PWMA));
     digitalWrite(DIRA_pin, HIGH);
  }
  else
  {
     analogWrite(PWMA_pin, PWMA);
     digitalWrite(DIRA_pin, LOW);     
  }  

  
  if (PWMB < 0)
  {
     analogWrite(PWMB_pin, abs(PWMB));
     digitalWrite(DIRB_pin, HIGH);
  }
  else
  {
     analogWrite(PWMB_pin, PWMB);
     digitalWrite(DIRB_pin, LOW);     
  } 
  

  if (PWMC < 0)
  {
     analogWrite(PWMC_pin, abs(PWMC));
     digitalWrite(DIRC_pin, HIGH);
  }
  else
  {
     analogWrite(PWMC_pin, PWMC);
     digitalWrite(DIRC_pin, LOW);     
  }  
 
}


int IR_Receive()
{
  int Status=0;
  while(pulseIn(IRpin, HIGH)  < SIGNAL_START)
  {
  }

  for(i=0; i < SIGNAL_SIZE; i++)
  {
    SignalWidth[i]=pulseIn(IRpin, HIGH);
  }

  yaw=0;
  pitch=0;
  throttle=0;
  trim=0;
  
  for( i=1; i < 8; i++)
  {    
    if (SignalWidth[i] > SIGNAL_0_min && SignalWidth[i] < SIGNAL_0_max)
      bitClear(yaw, 7-i); 
   else if (SignalWidth[i] > SIGNAL_1_min &&   SignalWidth[i] < SIGNAL_1_max)
      bitSet(yaw, 7-i);
   else
     //Serial.print("Error");  
     Status = -1;
  }
  
   for(i=1; i < 8; i++)
   {    
    if (SignalWidth[i+8] > SIGNAL_0_min && SignalWidth[i+8] < SIGNAL_0_max)
      bitClear(pitch, 7-i); 
   else if (SignalWidth[i+8] > SIGNAL_1_min &&   SignalWidth[i+8] < SIGNAL_1_max)
      bitSet(pitch, 7-i);
   else
          Status = -1;//Serial.print("Error");  
  } 
  
  for(int i=1; i < 8; i++)
  {    // il primo bit è sempre zero
    if (SignalWidth[i+16] > SIGNAL_0_min && SignalWidth[i+16] < SIGNAL_0_max)
      bitClear(throttle , 7-i); //viene trasmesso prima il più significativo
   else if (SignalWidth[i+16] > SIGNAL_1_min &&   SignalWidth[i+16] < SIGNAL_1_max)

      bitSet(throttle , 7-i);
   else
          Status = -1;//Serial.print("Error");  
  }
  
  for(int i=1; i < 8; i++)
  {    
    if (SignalWidth[i+24 ] > SIGNAL_0_min && SignalWidth[i+24] < SIGNAL_0_max)
      bitClear(trim , 7-i); 
   else if (SignalWidth[i+24] > SIGNAL_1_min &&   SignalWidth[i+24] < SIGNAL_1_max)
      bitSet(trim , 7-i);
   else
          Status = -1;//Serial.print("Error");  
  }
    
    // yaw è modificato da trim 
    int crz  = float(yaw)-0.3*float(trim-63);  
    crz = constrain(crz, 12, 115);  
    yaw = map(crz, 12, 115, 0, 127);  
    
    return Status;
}

About Boris Landoni

Boris Landoni is the technical manager of Open-Electronics.org. Skilled in the GSM field, embraces the Open Source philosophy and its projects are available to the community.