Wednesday, 19 August 2015

I2C Port Expander

For all of my projects I have used the standard digital output pins when connecting to the Armdroid's 8-bit parallel interface.  But, what if you want to free up some of these pins in order to use external sensors, controls, or other devices...

The easiest way of getting more inputs and outputs is to use an "i/o port expander".  This is a device that allows you to control a number of ports using data sent to the device.  A port expander takes the data and controls the appropriate I/O pins.  This allows lots of sensors and devices, including the ability to control multiple Armdroids using only a few pins on the Arduino board.

The device I chose was the PCF8574A which has eight general purpose input/output pins controlled using I2C (pronounced I-squared-C).  I2C is a serial communications protocol allowing ICs to swap data on the same two-wire bus - Serial Data Line (SDA) and Serial Clock Line (SCL).

There are other devices available supporting 16 ports (eg. MCP23017), or SPI buses (eg. MCP23S17) and are similar in usage.  Although SPI (10Mhz) is faster than I2C (1.7MHz), for our purpose interfacing the Armdroid, it doesn't really make much difference

Pinouts

PCF8574A pinouts
The expanded I/O port are Pins 4-7 and 9-12 to be connected to the Armdroid interface D1-D8

Three address pins A0, A1, A3 determine the chips ID and must be wired to either +5v or GND.

Pin 13 (INT) used for interrupt output - leave disconnected for now

SDA - SDA pin on Arduino
SCL - SCL pin on Arduino
Vcc - +5v on Arduino
GND - GND on Arduino plus Armdroid interface GND

The 8574A can be assigned an address in the range 0x38-0x3F, and you can change this address by changing the connections of the pins A0, A1, and A2 as shown in the following table:

A0
A1
A2
Address
GND GND GND 0x38
+5V GND GND 0x39
GND +5V GND 0x3A
+5V +5V GND 0x3B
GND GND +5V 0x3C
+5V GND +5V 0x3D
+5V +5V GND 0x3E
+5V +5V +5V 0x3F

You can use up to eight of these PCF8574A chips on the same IC2 bus.  Texas Instruments also manufacture the PCF8574 (without the A), essentially the same device with addresses ranging from 0x20-0x27.

You might find this i2c_scanner_sketch useful to verify your I2C communication is working if you have any problems.

Circuit Diagram


Circuit diagram for 8-bit Armdroid interface with PCF8574A

PCF8574A / Armdroid 1 connections:

P0 (pin 4) - D2 (J1/pin 4)
P1 (pin 5) - D1 (J1/pin 3)
P2 (pin 6) - D4 (J1/pin 6)
P3 (pin 7) - D3 (J1/pin 5)
P4 (pin 9) - D6 (J1/pin 8)
P5 (pin 10) - D5 (J1/pin 7)
P6 (pin 11) - D8 (J1/pin 10)
P7 (pin 12) - D7 (J1/pin 9)

J1/pin 1 (+5v) - not connected
J1/pin 2 (Gnd) - Common Gnd (Arduino & PCF8574A)

The address of the PCF8574A is hard-coded to 0x38 by grounding A0, A1, and A2.  I would recommend starting with these settings until you have something working.

Code

Surprisingly, the code changes to support the port expander in the Armdroid Library are minimal thanks to C++ inheritance, and the Arduino 'Wire' library.

When designing the Armdroid library, flexibility was foremost, and it was always my intention to allow the library to be easily adapted for use in different projects.

To make this possible, core functionality was implemented in an abstract base class which can be derived into specialized classes.  This base class doesn't actually have any knowledge how control the hardware, rather, its up to the derived classes to implement that.  Another potential use in future might include supporting optimized versions for different Arduino boards.

In our case, we're creating a new class ArmdroidPortExp inheriting from ArmBase to represent our port expander variant:

 class ArmdroidPortExp : public ArmBase {  
  public:  
  void begin(uint8_t address = 0x38);  
  protected:  
  void armdroid_write(uint8_t output);  
  private:  
  uint8_t i2c_addr;  
 };  

This implementation adds a new member function ArmdroidPortExp::begin(uint8_t address) responsible for setting up the I2C hardware, and stores the supplied address of the port expander.  We also take the opportunity to initialize the Armdroid interface at this point.

The virtual function ArmdroidPortEx::armdroid_write(uint8_t output) simply writes a byte to the port expander using the address supplied earlier.  This is all done using the 'Wire' library that conveniently abstracts us from low-level I2C protocol/transmission details:

 void ArmdroidPortExp::begin(uint8_t address)  
 {  
  i2c_addr = address;  
  Wire.begin();  
  Wire.beginTransmission(i2c_addr);  
  Wire.write(STROBE);  
  Wire.endTransmission();  
 }  
 void ArmdroidPortExp::armdroid_write(uint8_t output)  
 {  
  Wire.beginTransmission(i2c_addr);  
  Wire.write(output);  
  Wire.endTransmission();  
 }  

The implementation really is that simple !

Finally, here's a snippet of code that instantiates and uses the newly derived class...

 #include <Wire.h>  
 #include "Armdroid.h"  
 #include "ArmdroidPortExp.h"  
 // port expander address:  
 const int i2c_addr = 0x38;  
 // initialize Armdroid library:  
 ArmdroidPortExp myArm;  
 void setup()  
 {   
  myArm.begin(i2c_addr);  
  myArm.setSpeed(120);  
  myArm.torqueMotors(true);  
 }  
 void loop()  
 {  
      .  
      .  
      .  
 }  

I've added this code as an extension to the Armdroid Library as a separate package:
https://github.com/Armdroid/Armdroid-Arduino-PortExp

It wasn't possible to add this code to the library without introducing a dependency on the Wire library due to an 'oddity' with the Arduino IDE build mechanism.  If you intend to make use of these extensions, you will need to first install the Armdroid-Arduino-Library, then follow the instructions to install Armdroid-Arduino-PortExp

Included with this library is a modified version of the AsyncDemo previously presented with the Asynchronous library enhancements.

Photographs of my test circuit for reference:

See Also

PCF8574A data sheet: http://www.ti.com/lit/ds/symlink/pcf8574a.pdf
I2C tutorial: http://www.robot-electronics.co.uk/i2c-tutorial
Arduino wire library: https://www.arduino.cc/en/Reference/Wire

Standard Armdroid / Arduino wiring:
http://armdroid1.blogspot.co.uk/2014/02/interface-bench-test-part-1.html
http://armdroid1.blogspot.co.uk/2014/02/interface-bench-test-part-2_15.html
http://armdroid1.blogspot.co.uk/2014/02/interface-bench-test-part-3.html

Sunday, 9 August 2015

Asynchronous Enhancements

I've been making several enhancements to Armdroid Library to support Asynchronous operation.  This means its now possible to drive all motors, and still do other processing at the same time - for example listening to the serial port for stop requests etc.

You may recall in my last post, when driving motors for very long durations, its simply not possible to interrupt things until all motors reached their target positions.  This has been a limitation for sometime - and with hindsight, I really wish I had implemented the library slightly differently in the first place.

Advanced users will welcome these changes, although you can still use the existing drive methods which have been retained for compatibility with existing sketches/projects.

GitHub: https://github.com/Armdroid/Armdroid-Arduino-Library

The new drive method is:
 void driveMotorsAsynchronous()

and complimented by the following control methods:
ArmAsyncState getAsyncState()
bool isRunning()
bool Start(MTR_CHANNELS target)
bool Pause()
bool Resume()
bool Stop()

To asynchronously drive motors, you must call driveMotorsAscynchronous() in your sketch loop() function.  This handles timings and pulses stepper motors when ArmAsyncState = ASYNC_DRIVE_RUNNING.  To enter this state, you call Start() with arguments to specify the target position for each motor channel.
Multiple calls to Start() are possible, but the current implementation does not wait for the existing target positions to be reached before running to new targets.  This behavior was intentional as you can always implement a data structure to cache requests as necessary.

To cancel running motors at any time, call the Stop() method which cancels the asynchronous run request, and stops all motors dead.

Likewise, Pause()/Resume() allows you to pause and resume movements, but does not cancel the asynchronous run request.

The asynchronous extensions implements a simple state machine, so the ArmAsyncState enumeration has been provided to allow programmers to evaluate the state at any time.

To demonstrate the concept - the following example shows how to send commands over serial and allows users to cancel movements, at any time, no matter how lengthy the motor operations are.

You can send bytes to the Arduino from any software that can access the computer serial port.

Code

 /*  
  * Asynchronous Drive Demonstration  
  * Copyright Richard Morris 2015. All Rights Reserved  
  * http://armdroid1.blogspot.co.uk  
  *  
  */  
 #include "Armdroid.h"  
 // initialize Armdroid library:  
 Armdroid myArm( 2, 3, 4, 5, 6, 7, 8, 9 );  
 // variables for receiving and decoding commands:  
 unsigned int rxCmdPos;  
 int rxCmdVal;  
 int rxCmdArg[5];  
 // variable to store previous state:  
 ArmAsyncState previous;  
 void setup()  
 {  
  Serial.begin(9600);  
  myArm.setSpeed(120);  
  myArm.torqueMotors(true);  
 }  
 void loop()  
 {  
  if (!Serial) {  
   // while the serial stream is not open, do nothing:  
   while(!Serial);  
   Serial.println(F("Welcome, Armdroid!"));  
   // reset command variables  
   rxCmdPos = rxCmdVal = 0;  
   memset(rxCmdArg, 0, sizeof(rxCmdArg));  
  }  
  if (Serial.available()) {  
   const char ch = Serial.read();  
   if (isDigit(ch))  
    rxCmdVal = (rxCmdVal * 10) + (ch - '0'); // accumulate value  
   else if (ch == '-')  
    rxCmdVal = rxCmdVal * -1;  
   else if (ch == ',') {  
    rxCmdArg[rxCmdPos++] = rxCmdVal;      // shift received value into  
                          // arguments array  
    if (rxCmdPos == 6)  
     rxCmdPos = 0;              // wrap argument index  
    rxCmdVal = 0;               // reset accumulator  
   }  
   else if (ch == 'D') {  
    Serial.println( F("drive motors") );  
    MTR_CHANNELS channels = { rxCmdArg[0], rxCmdArg[1], rxCmdArg[2], rxCmdArg[3], rxCmdArg[4], rxCmdVal };  
    myArm.Start(channels);  
    // reset accumulator  
    rxCmdPos = rxCmdVal = 0;  
   }  
   else if (ch == 'P') {  
    Serial.println( F("pause running motors") );  
    myArm.Pause();  
   }  
   else if (ch == 'C') {  
    Serial.println( F("continue driving motors") );  
    myArm.Resume();  
   }  
   else if (ch == 'S') {  
    Serial.println( F("stop dead all motors!") );  
    myArm.Stop();  
   }  
  }  
  // method called every loop iteration:  
  myArm.driveMotorsAsynchronous();  
  // report state changes:  
  ArmAsyncState current = myArm.getAsyncState();  
  if (current != previous) {  
       Serial.print( F("DRIVE STATUS = ") );  
       switch (current)  
       {  
            case ASYNC_DRIVE_STOPPED:  
                 Serial.println( F("STOPPED") );  
                 break;  
            case ASYNC_DRIVE_RUNNING:  
                 Serial.println( F("RUNNING") );  
                 break;  
            case ASYNC_DRIVE_PAUSED:  
                 Serial.println( F("PAUSED") );  
                 break;  
       }  
       previous = current;  
  }  
 }  

for example - drive base motor 2,500 steps counter-clockwise
issue:   0,0,0,0,0,2500D
at any time, send (P)ause, (C)ontinue, or (S)top

I'm currently working on a revised specification for the Armdroid Serial Protocol to include these changes and streaming modes.   In the meantime, I've included this demonstration (AsyncDemo) in the examples directory.

Monday, 13 July 2015

Wi-Fi-controlled Armdroid

Back during Easter Bytes at TNMOC, a visitor to the museum asked if it's possible to control the Armdroid on display using their Mobile Phone or Tablet Device...   This got me thinking, and decided to hack something together for the following weekend...

If your interested in controlling your Armdroid using Wi-Fi or across the internet, here's how you can setup your own stand-alone, web-enabled Armdroid, using the Arduino Yun.   When you're done, you'll be able to control your Armdroid using any web-browser, or control programmatically over the internet using Python or other scripting languages.

What exactly is the Arduino Yun you might be asking.... It's basically a combination of a classic Arduino Leonardo (based on the ATmega32U4 microcontroller) with a WiFi system-on-a-chip Atheros AR9331 running Linino (a MIPS GNU/Linux distribution based on OpenWrt).  The two processing units are connected together using the Bridge library allowing you to combine the power of Linux with ease of Arduino.

OpenWrt supports REST services for clients and servers.  REST is an acronym for "Representational State Transfer".  It is a software architecture that exposes functionality through URLs.  REST has gained widespread acceptance across the World Wide Web as a simple alternative to SOAP and WSDL-based web services.   A nice introduction to the concepts behind REST can be found here.

This project implements an Armdroid REST API allowing functions of the robotic arm to be manipulated through URLs.  I've prepared a simple web page that consumes this service to get you going, although you could easily interact with this from say, a Raspberry PI using the CURL library, etc.


The photographs above shows the project from Easter Bytes.  In this arrangement, we configured the Yun's WiFi as a standalone Access Point - visitors would simply connect to this network using a Mobile Phone web-browser, then take control.   This actually proved to be a real hit with many visitors to the museum, and was especially rewarding to get usability feedback from a 7-year old! (pictured)

You can see the simplicity of the set-up in the following photographs:

I've added the source code for this project to the Armdroid Library examples directory  https://github.com/Armdroid/Armdroid-Arduino-Library

If you wish to use the sample web page, you'll need to prepare a memory card by creating the directory structure "arduino/www" which ensures the Yun will create a link to the SD card "/mnt/sd" path.

The REST API is structured around verbs and Armdroid functions - for example, you want to move the shoulder stepper motor x steps, you would simply issue an HTTP web request like http://arduino/armdroid/shoulder/position/x.   Likewise, other Armdroid functions are described by their function - base, elbow, gripper etc.

If you wish to determine what is the current location for any stepper motor, you would use a URL such as http://arduino/armdroid/shoulder/position (without position value) and this will return in the response the offset counter for this channel.

The complete REST API comprises of the following URL structure:
  • http://myArduinoYun.local/arduino/armdroid/base/position : returns base motor offset counter
  • http://myArduinoYun.local/arduino/armdroid/base/position/x : rotates base motor x steps clockwise or counterclockwise
  • http://myArduinoYun.local/arduino/armdroid/base/position/x/y : rotates base motor x steps clockwise or counterclockwise at y revolutions-per-second
  • http://myArduinoYun.local/arduino/armdroid/base/sensor : returns base sensor reading
  • http://myArduinoYun.local/arduino/armdroid/shoulder/position : returns shoulder motor offset counter
  • http://myArduinoYun.local/arduino/armdroid/shoulder/position/x : rotates shoulder motor x steps clockwise or counterclockwise
  • http://myArduinoYun.local/arduino/armdroid/shoulder/position/x/y : rotates shoulder motor x steps clockwise or counterclockwise at y revolutions-per-second
  • http://myArduinoYun.local/arduino/armdroid/shoulder/sensor : returns shoulder sensor reading
  • http://myArduinoYun.local/arduino/armdroid/elbow/position : returns elbow motor offset counter
  • http://myArduinoYun.local/arduino/armdroid/elbow/position/x : rotates elbow motor x steps clockwise or counterclockwise
  • http://myArduinoYun.local/arduino/armdroid/elbow/position/x/y : rotates elbow motor x steps clockwise or counterclockwise at y revolutions-per-second
  • http://myArduinoYun.local/arduino/armdroid/elbow/sensor : returns elbow sensor reading
  • http://myArduinoYun.local/arduino/armdroid/wrist/pitch: returns both LHS and RHS wrist offset counters
  • http://myArduinoYun.local/arduino/armdroid/wrist/pitch/x : counter-rotates wrist motors to pitch gripper up/down
  • http://myArduinoYun.local/arduino/armdroid/wrist/pitch/x/y : counter-rotates wrist motors to pitch gripper up/down at y revolutions-per-second
  • http://myArduinoYun.local/arduino/armdroid/wrist/rotate : returns both LHS and RHS wrist offset counters
  • http://myArduinoYun.local/arduino/armdroid/wrist/rotate/x : rotates wrist motors to roll gripper clockwise/counterclockwise
  • http://myArduinoYun.local/arduino/armdroid/wrist/rotate/x/y : rotates wrist motors to roll gripper clockwise/counterclockwise at y revolutions-per-second
  • http://myArduinoYun.local/arduino/armdroid/wrist/left/position : returns LHS wrist offset counter
  • http://myArduinoYun.local/arduino/armdroid/wrist/left/position/x : rotates LHS wrist motor x steps clockwise or counterclockwise
  • http://myArduinoYun.local/arduino/armdroid/wrist/left/position/x/y : rotates LHS wrist motor x steps clockwise or counterclockwise at y revolutions-per-second
  • http://myArduinoYun.local/arduino/armdroid/wrist/left/sensor : returns LHS wrist sensor reading
  • http://myArduinoYun.local/arduino/armdroid/wrist/right/position : returns RHS wrist offset counter
  • http://myArduinoYun.local/arduino/armdroid/wrist/right/position/x : rotates RHS wrist motor x steps clockwise or counterclockwise
  • http://myArduinoYun.local/arduino/armdroid/wrist/right/position/x/y : rotates RHS wrist motor x steps clockwise or counterclockwise at y revolutions-per-second
  • http://myArduinoYun.local/arduino/armdroid/wrist/right/sensor : returns RHS wrist sensor reading
  • http://myArduinoYun.local/arduino/armdroid/gripper/position : return gripper motor offset counter
  • http://myArduinoYun.local/arduino/armdroid/gripper/position/x : rotates gripper motor to open/close fingers
  • http://myArduinoYun.local/arduino/armdroid/gripper/position/x/y : rotates gripper motor to open/close fingers at y revolutions-per-second
  • http://myArduinoYun.local/arduino/armdroid/gripper/sensor : returns gripper motor offset counter
  • http://myArduinoYun.local/arduino/armdroid/torque/enabled : returns a value indicating torque has been applied to all motors
  • http://myArduinoYun.local/arduino/armdroid/torque/enabled/x : enables/disables torque
  • http://myArduinoYun.local/arduino/armdroid/home : returns to home (starting) position
All responses are returned using JSON (JavaScript Object Notation) formatted replies allowing simplified parsing in many programming languages.

If your interested to understand how the code works - you need to first understand how the Bridge example works as a starting point - see https://www.arduino.cc/en/Tutorial/Bridge

Basically, each of the six channels (base/shoulder/elbow/left wrist/right wrist/gripper) are implemented using a command method as follows:

 void baseCommand(YunClient client)  
 {  
  String baseCmd = client.readStringUntil('/');  
  if (baseCmd.startsWith("position")) {  
   // read number of steps, if none have been specified, parseInt()  
   // will simply return zero which will be ignored, and we'll  
   // finish by feeding back current position to the client.  
   const int steps = client.parseInt();  
   if (steps != 0) {  
    // if the URL includes a speed value, use it  
    int whatSpeed = DEF_MOTOR_SPEED;  
    if (client.read() == '/') {  
     const int speedInRpm = client.parseInt();  
     if (speedInRpm > 0)  
      whatSpeed = speedInRpm;  
    }  
    // drive motor to rotate base clockwise/counterclockwise  
    driveMotor( ARMDROID_BASE_CHANNEL, steps, whatSpeed );  
   }  
   // send feedback to client  
   client.print(F("{\"base-position\":"));  
   client.print(getPositionReading( ARMDROID_BASE_CHANNEL ));  
   client.println(F("}"));  
  }  
  else if (baseCmd.startsWith("sensor")) {  
   client.print(F("{\"base-sensor\":"));  
   client.print(getSensorReading( ARMDROID_BASE_CHANNEL ));  
   client.println(F("}"));  
  }  
  else  
   client.println(F("ERROR: invalid base command"));  
 }  

The code begins by reading the client URL and tries to match the "position" command.  If this is present, we can then determine if the URL is specifying a value representing the position in which to drive the motors, or alternatively, we'll simply report on the current position.  If we do have a valid "position" command then we look to see if a speed has been specified.  If this is not the case, the default (120 RPM) will be used instead.

If the command matches "sensor" then we have the ability to return the current sensor reading for the given channel.

In either case, after calling the Armdroid Library to perform the desired function, we send feedback to the client such as the new position after the operation completes.


Limitations

Yun's web server times-out after approximately 5seconds when performing lengthy Armdroid operations.  This is mostly due to the design of the current Armdroid Library which is synchronous in operation, or blocking, when running motors for long periods of time.  In this situation, a response doesn't get returned from the microcontroller before the web server gives up, and returns 500 (Internal Server Error) back to the client.

This isn't immediately obvious using the example web page, although can be easily demonstrated when browser debugging is enabled:
Fig 1. short base rotation (200 steps)
Issuing the request /arduino/armdroid/base/position/200 rotates the base stepper motor 200 steps resulting in HTTP status code 200 (OK) returned.  You can see the reply includes our JSON response indicating the position (current offset counter) of this motor channel after performing the operation.

Now observe what happens when the request is made with 2000 steps:
Fig 2. lengthy base rotation (2,000 steps)
HTTP status code 500 is returned after 5seconds, and before the motor has finished running.  By the time the motor reaches the target position, the connection no longer exists, no response is returned.


We'll revisit this when Asynchronous enhancements have been added to the library, but in the meantime, you can work around this by calling the API to retrieve the current position and wait if necessary until the previous operation has completed.

The sample web page included with the example is very basic...  It serves purely as a demonstration only... if you want something with more sex appeal, you might want to look at DoJo or similar frameworks to create a richer experience.

Tuesday, 13 January 2015

Infrared Remote Control

I realise it's been a long time since writing anything here...

Today, I'm going to share with you my Armdroid Remote Controller project, which was quickly hacked together for demonstrating the Armdroid 1 during a recent meeting of volunteers at TNMOC.

The circuit makes use of a single TSOP4838 IR receiver module along with some rather cleaver programming that decodes key presses and operates various Armdroid functions.

I'll be including the source code for this project as an example sketch in the forthcoming release of Armdroid Library on GitHub.

The project works surprisingly well, although movements to preset positions are made point-2-point (P2P), which is slightly different to continuous path movements.  This is after all, a demonstration, although could be easily extended to support waypoints and recording/playback of movements.

I've been using an old Sky remote control handset, you'll need to change the scan codes to match your hardware, although instructions will be included how to do this.

Connecting an infrared receiver module is relatively simple, the sensor output is connected to a spare digital input on the microcontroller.

ArmdroidShield (version 1) utilizes pins 2-9 for connecting to the Armdroid's 8-bit parallel interface, so pin 10 was chosen for this purpose, along with +5V and Gnd connections.  There are many common IR receiver modules available.  Check the datasheet for your device to ensure that you connect it correctly.

The software works by decoding the IR signals to digital pulses that correspond to buttons on the remote.  A scan code lookup table is then used to assign Armdroid functions to key presses.   This table uses function pointers to simplify the program logic, also included are methods for Rolling/Pitching the Gripper, along with routines for calculating target offsets when moving to new positions.