Tuesday, 2 December 2014

A Prototype Robot Arm Controller

The small robot arm kits available these days are both fun to build but also are simple enough to open doors into the world of home robotics.

The robot arm in its untouched state

I also have an Arduino I bought some time ago. While it was interesting to play with small samples, controlling LEDs on a breadboard for example, having the robot meant I could try something more involved.

A search on Google for ideas came up with the following article about using capacitive distance sensing to make a 3D hand sensor Instructables 3D Controller.  I figured this would give quite an interesting demo of using my arm to move the robot arm.

The first step was to create the 3D sensor. The instructions are fortunately straightforward and apart from refreshing some rusty soldering skills I encountered no issues.

The question about how I would be able to use the sensor to control motions in two directions along each axis was an interesting one. A simple analysis of the response in the Arduino serial monitor showed that the signal rose at an increasing rate closer to the sensor, However at a point about 1/3 of the distance from each sensor to the edge of the cube a movement of 1-2 inches would result in a change of signal of 5 units in each direction. I could use this as the basis of the controller.

My approach was to program a 2 second wait after an initial upload or reset of the Arduino, I could use this time to put my hand in the correct position within the sensor cube. At the end of the 2 seconds any movement that changed the response by greater than +/- 5 units would cause the arm to move along that axis in the corresponding +/- direction.

Finally, I needed a motor shield for the Arduino. For this prototype I stuck with the standard version which provides control over two motors. This would obviously restrict the scope of the project since the robot arm itself has 5 motors.

The completed prototype is shown below. I used the X axis to control the grabber and the Z axis to control front and back movement.

The completed prototype
What were the problems?

Well, to begin with the sensor was too unforgiving. You had to begin the session with your hand just in the right place. This isn't a good situation from a user perspective.

The processor loop doesn't allow two axes to be engaged simultaneously.

Finally, the sensor is also subject to noise such that the response changes over time.

I'll not be taking this approach any further but this has been a good project in terms of putting together several technologies. The arm certainly won't be mothballed!

The code for the Arduino is given below:

// Based on a sample by By Kyle McDonald
// From the instructables project at:
// http://www.instructables.com/id/DIY-3D-Controller/

#define resolution 4
#define mains 50 // 60: north america, japan; 50: most other places

#define refresh 2 * 1000000 / mains

/*
Digital Pins:
  12, 3, 9 Channel A
  13, 11, 8 Channel B
  5,6,7 Sensor
*/

int xSensor = 5;
int ySensor = 6;
int zSensor = 7;
int baseX = 0;
int baseY = 0;
int baseZ = 0;

void setup() {
  Serial.begin(115200);

  //Setup Channel A
  pinMode(12, OUTPUT);    // Initialise Motor Channel A pin - DIRECTION
  pinMode(9, OUTPUT);     // Initialise Brake Channel A pin - BRAKE

  //Setup Channel B
  pinMode(13, OUTPUT);    // Initialise Motor Channel A pin - DIRECTION
  pinMode(8, OUTPUT);     // Initialise Brake Channel A pin - BRAKE

  pinMode(xSensor, INPUT);
  pinMode(ySensor, INPUT);
  pinMode(zSensor, INPUT);
 
  startTimer();
 
  // Wait 2 seconds before we start moving. To allow you to get your hand in place
  delay(2000);
}

void loop() { 
  int xPos = time(xSensor, B00100000);
  // For some reason the first xPos comes back as -1
  if (baseX == 0 && xPos > 0)
  {
    baseX = xPos;
  }
 
  int yPos = time(ySensor, B01000000);
  if (baseY == 0)
  {
    baseY = yPos;
  }

  int zPos = time(zSensor, B10000000);
  if (baseZ == 0)
  {
    baseZ = zPos;
  }
 
  if (yPos  - baseY > 5)
  {
     Serial.println("Y up");
     digitalWrite(12, HIGH);  // Establish forward direction of Channel A
     digitalWrite(9, LOW);    // Disengage the break for Channel A
     digitalWrite(8, HIGH);   // Engage the Brake for Channel B
     analogWrite(3,244);      // Spin the motor on Channel A at full speed
  }
  else if (yPos - baseY < -5)
  {
    Serial.println("Y down");
    digitalWrite(12, LOW);    // Establishes backward direction of Channel A
    digitalWrite(9, LOW);     // Disengage the Brake for Channel A
    digitalWrite(8, HIGH);    // Engage the Brake for Channel B
    analogWrite(3, 244);      // Spin the motor on Channel A at half speed
  }
  else if (xPos  - baseX > 5)
  {
     Serial.println("X up");
     digitalWrite(13, HIGH);  // Establish forward direction of Channel B
     digitalWrite(8, LOW);    // Disengage the break for Channel B
     digitalWrite(9, HIGH);   // Engage the Brake for Channel A
     analogWrite(11,244);     // Spin the motor on Channel B at full speed
  }
  else if (xPos - baseX < -5)
  {
    Serial.println("X down");
    digitalWrite(13, LOW);    // Establishes backward direction of Channel B
    digitalWrite(8, LOW);     // Disengage the Brake for Channel B
    digitalWrite(9, HIGH);    // Engage the Brake for Channel A
    analogWrite(11, 244);     // Spins the motor on Channel B at half speed
  }
  else
  {
    Serial.println("Neutral");
    digitalWrite(8, HIGH);    // Engage the Brake for Channel B
    digitalWrite(9, HIGH);    // Engage the Brake for Channel A
  }
}

long time(int pin, byte mask) {
  unsigned long count = 0, total = 0;
  while(checkTimer() < refresh) {
    // pinMode is about 6 times slower than assigning
    // DDRB directly, but that pause is important
    pinMode(pin, OUTPUT);
    PORTD = 0;
    pinMode(pin, INPUT);
    while((PIND & mask) == 0)
      count++;
    total++;
  }
  startTimer();
  return (count << resolution) / total;
}

extern volatile unsigned long timer0_overflow_count;

void startTimer() {
  timer0_overflow_count = 0;
  TCNT0 = 0;
}

unsigned long checkTimer() {
  return ((timer0_overflow_count << 8) + TCNT0) << 2;
}





Tuesday, 22 July 2014

Old Hardware - Part 2

There were a couple of things to finish off before the Linux laptop was ready for use, solving the problem of storage and setting up an internet connection.

Storage was simple. The machine has a 20GB hard drive, of which 6GB had been partitioned to Linux. Once I had a working build, I lowered the Windows partition down to 10GB and created a new partition for scratch data storage.

I also had an HP Pavilion laptop that suffered from overheating. This was a widespread problem and HP patched the BIOS to keep the fan on. However, my machine broke with one of the common symptoms of the WiFi stopping. Incidentally as it was still under warranty HP were great about this and from shipping to return took only about 3 working days. Unfortunately, the underlying problem was still present and about 6 months later the video card broke.

At this point, the machine was out of warranty and I bought a new one (not HP). I did however have a perfectly good 250 GB hard drive. Amazon do pretty cheap 2.5in SATA to USB enclosures so I purchased one of these and got myself a 250GB USB drive.

The next step was to reformat the drive from NTFS to FAT32 so it could be used by Windows and Linux. Windows of course does not easily format FAT32 drives greater than 32GB but by good fortune the EaseUS partition master I used to partition the laptop also formats large drives to FAT32.

Secondly, the Wifi.

2002-era laptops likely didn't come with built in WiFi. Mine at least had a modem! At the time when I first got WiFi I installed a PCI card from BT. This didn't work after the default Lubuntu installation so I needed to set-up the card separately.

Instructions are widespread on the web. First, you need to discover the available PCI devices. The command to do this is:
lspci -vvnn | grep 14e4

From this I determined that the card was a Broadcom BCM4318.

Setting this up offline involves the following steps: 

As this is the b43 chipset install b43-fwcutter from the installation CD.



 Run the following commands:

~$ tar xfvj broadcom-wl-4.150.10.5.tar.bz2
~$ sudo b43-fwcutter -w /lib/firmware wl_apsta-3.130.20.0.o

~$ sudo b43-fwcutter --unsupported -w /lib/firmware broadcom-wl-4.150.10.5/driver/wl_apsta_mimo.o

Finally restart the machine.

I now had a working Linux laptop with WiFi and 250 GB of storage available.




Thursday, 10 July 2014

Rejuvenating old hardware

I have an old Packard Bell Easynote One laptop I bought way back in 2002 (it wasn't even cutting edge then). When XP started slowing down under  the weight of windows services it ended up hidden, like so many other old machines, in the back of a cupboard. I think the last straw was trying to install iTunes.

I'm not one to be happy with perfectly good hardware going to waste so I eventually decided to get back into the world of Linux and install a lightweight distro to see if the laptop could be of some use. In this case I decided on Lubuntu.

On firing it up I discovered that the CD-ROM drive had died. This ruled out installing from a boot CD. Fortunately though I have an external USB CD-ROM so I decided to go down the route of setting up a dual booting system.

First step was to repartition the hard drive. This is a paltry 20GB. (Plenty of phones with more these days!) Bearing in mind the intention was not to use XP, I spent some time deleting data and uninstalling everything I could from XP. I got down to about 10GB, I'm sure I can do better...

After this I installed the free Partition Master Home Edition from EaseUS and used it to create a new Partition of 6GB. Some more partitioning would happen later.

Next step was to setup Grub. This is a boot manager that can be used to boot multiple operating systems.
  1. Create a new C:\boot folder, copy initrd.gz and vmlinuz from the Lubuntu distro
  2. Download Grub4Dos and copy grldr to C:\ and menu.1st to C:\boot\grub
  3. Edit menu.lst and add the following to the bottom
    title Ubuntu Installer (hd0,1)
    kernel (hd0,1)/boot/vmlinuz
    initrd (hd0,1)/boot/initrd.gz
    Note hd0,1 refers to the second partition on the hard disk, i.e. Windows (The first partition is hidden)
  4. Edit boot.ini (set  attrib -a -r -s -h c:\boot.ini first). Add C:\grldr="Start GRUB"
  5. Put the distro CD into the USB CD-ROM and restart, select Start GRUB and follow the instructions to install
And here it is back in use.

Wednesday, 9 July 2014

Small beginnings

We're now well into the 21st century and finally I've decided to start a blog.

What's it going to be about? Well I work in software development and technology is something I really love. This means that amongst other things I tinker around a bit in my spare time. Sometimes I even succeed in learning something or creating something cool.

Also, a fair few years ago I completed my PhD in Particle Physics. I spent a good part of that time working for the ZEUS experiment on the HERA accelerator in Hamburg. Of course the reasons why I loved Physics, and Science in general are still with me.

So I'm going to be writing about a fair few technical tips I've learnt over the years, mixed with some science stuff.

Finally, JSco? It's the name I now have at work - from a time when I worked in a team with lots of other Jonathans.  The name stuck and I like it!