Tuesday, April 29, 2014

Blistex Linear Actuator

Update:  5/19

I ordered a servo rotary driver from Servo City (part # SR500) which is a better way of coupling the leadscrew to the servo hub.  With a bit of modification, I was able to attach the leadscrew in a very permanent manner.  After reassembling and a slight modification to the location of the "in" magnetic sensor, the linear actuator is working great.

In the following video, I manually start the servo moving in or out, but it automatically stops at the limits (you can see the limit sensor values change from 1 to 0).  Near the end, I demonstrate that you can also use S to stop the actuator at locations other than the endpoints.  The code is the same as before.

Original Post

This project has been on my "to-do" list for quite a while.  All credit to the Matt Peick:  http://makezine.com/projects/make-34/the-mighty-lip-balm-linear-actuator/ .   Although it's a pretty simple build, I've had problems getting the lip balm "leadscrew" to attach to the servo shaft.  I've tried several kinds of glue (JB Weld*, Bond 527 cement) and tried to get a screw between the two of them but the leadscrew diameter is too small.  As a result, the linear actuator can push out fine, but the leadscrew pops off the servo as the linear actuator pulls in.  You can see this in the video below where the sound of the servo changes noticeable when the leadscrew comes off and the servo is now free-spinning.

I've ordered a couple items from ServoCity and I'm hoping one of these will allow me to make a better connection.  But for now, my implementation of this idea is not ready for usage.

Custom code for this item is on Github:  it allows in and out motion to be triggered using a serial window and also gives a bit of troubleshooting data (the sensor values and the servo pulse width).  https://github.com/gcronin/GPSLockbox/blob/master/blisterservo/blisterservo.ino

*Note: JB Weld is surprisingly poor at holding together plastics, especially smooth ones!

GPS Lockbox: Code Done, Assembly Begun

Last time I posted about the "guts" of a lockbox which would open when a certain location was reached.  The user would input a destination, start the program, the box would lock, and would not open again until the user were close enough to the destination.

I had worked out the details of reading the GPS, using the keypad through a GPIO I2C board, using a 8x2 LCD, and using some RGB lights as indicators.  Since that point, I have upgraded to a 16x2 LCD, and greatly upgraded the code, which now asks for user input (a destination location), calculates the distance to the location, modifies the RGB lights to have a color proportional to the distance to target (from blue == far away to red == close), and "unlocks" the box when close enough.

Original prototype for a 16x2 breakout board
for a Sparkfun LCD.  This is the one I am using
for the GPS lockbox.
The first modification was to use a 16x2 LCD from Sparkfun.  Data sheet is here.   It is SO much bigger than the 8x2, and it operates in 4bit mode meaning that only four of the eight data lines (DB0 - DB7) are needed to "run" the LCD.  I made a PCB breakout board for this one:  the Eagle data files can be found here. This process also opened up four more pins on the Arduino Micro... Enable, R/W, DB4, DB5, DB6, and DB7 are running to the analog inputs, freeing up four of the digital pins.

For the newer prototypes, the potentiometer
is underneath the LCD to minimize space.

I had previously had some confusion about the differences between different ways to represent GPS data.  I have standardized the code such that it always represents data in the format dd mm.mmmm.  This happens both when data is printed back to the computer over the serial port, and also when data is printed on the LCD.  This format can also be found easily online in many places like GPS Visualizer (see the picture below).  So this should make the process of inputting a destination pretty easy.

Next I added in code which allows the user to put in the target destination.  The most current version of the code is on GitHub.     The flowchart below shows the way that the keypad is read.  This is important, because it takes about a second to read the GPS and we only want this to be happening when the user is not entering data.  Otherwise the delay is impossibly confusing.  In addition, the flowchart always allows us to "start over" at any point by pressing A, so that we can always enter a new destination without having to restart the microprocessor.

The right hand side of the code is very similar to what I had in the first iteration.  The biggest change is that I realized that I needed to look for the $GPGGA ANYWHERE in the GPS string and not just at the beginning of the string.  I had thought my previous code did this, but I was wrong.  The line below looks for $GPGGA and also looks for an astericks after $GPGGA.  Since $GPGGA is at the start of the string and * indicates the checksum, the Parse flag is only set if a full string has been seen.

 if(StringContains(buffer, "$GPGGA")!= -1 && StringContains(buffer, "*") > StringContains(buffer, "$GPGGA")) {
        startPosition = StringContains(buffer, "$GPGGA") +7;
        Parse = true;

The distance function on the right side is one that I just pulled directly from the internet.  I have no idea how it works; has to do with finding the distance along a great circle between the current location and the destination location.

The rest of the code is straightforward although the implementation is pretty course.  The Setup Program drops the Latitude Setup Flag, prints a prompt on the LCD for the operator ("Latitude: ddmmmmmm"), zeros the input buffer, and turns the LEDs red to show that we are waiting for input.

The Latitude Setup Program reads in the latitude as it is typed (slowly shifting the LEDs towards green) and prints the inputs on the LCD so that they replace the ddmmmmmm.  It takes 8 digits, then prompts the user to "Hit D".  Hitting D raises the Latitude Setup Flag, drops the Longitude Setup Flag, prints a prompt on the LCD for the operator ("Longitude: dddmmmmmm"), turns the LEDs red, and zeros the input buffer.  The Longitude Setup Program does the same thing as the Latitude Setup Program, raising the Setup Flag at the end.

The structure of the program allows the user to type A at any time which will lower the Setup Flag (bypassing GPS readings until the flag is set again) and allowing the setup program to run again in its entirety.

The last piece of this puzzle has to do with the RGB LEDs.  Originally I had hoped to have them fade throughout the program with the rate of fading increasing as the user got closer to the target destination.  While I updated the function fadeColor to be asynchronous (so the colors would change as the main loop cycled without a separate loop within the function), I found that the fade rate was limited by the loop cycle time, which was relatively slow because of the time required to read the GPS.  Fades either go impossibly slow OR huge jumps in color must be done each loop, which looks stupid.  So I will only use the fading at the very end as a sort of celebration once the box is opened.  Otherwise, once setup is done, the color of the LEDs is just set on a scale between RED (255, 0, 0) or BLUE (0, 0, 255) using a scale factor (eventually i'll program in the user to enter the approximate distance to the target and that will be the scale factor).  The variable "precision" is a threshold for how close the user needs to be to the target before the box opens.

I am working on putting all the components into a hinged box bought from Staples, product #942401.  Fortunately it laser prints beautifully.  I will have to figure out how the box physically locks.

Sunday, April 13, 2014

GPS Lockbox "Guts"

Often with complex projects which involve a lot of different components which have to talk to each other, I will work on the guts in stages without worrying about what the final product will look like.  Using getting the guts to work together is a huge part of the project and the cosmetic details are a secondary consideration.  If the "insides" don't work, it doesn't matter how pretty the outsides look.

This project is envisioned to be a modification of the lockbox which allows the box to open when a certain location is reached (rather than when a certain code is entered).  The lockbox then is a little like the inverse of going geo-caching.  Rather than knowing the GPS coordinates but not having the item at those coordinates, you have the item but not the coordinates.

The guts of this project are shown in the picture at left.  The GPS modem (MediaTek 3328, labelled GTPA010, datasheet here) returns time, latitude, longitude, and altitude at regular intervals (2-3 seconds or so) using a serial connection.  The LCD is the same one I used for the calculator, although I made a nice PCB board for it to simplify the connection process.  Eagle CAD files for the board are here.  This is necessary because the LCD board requires 8-bit mode and therefore has 16 pins and requires 10 inputs plus +5 and GND.   The board even has a spot for a 10k potentiometer so you can adjust the contrast of the LCD.  The keyboard is the same one used for the lockbox and the calculator, but this time I've routed it through a I2C GPIO board from dfrobotics.   The RGB LEDs will be used to provide the user with visual feedback of some type depending on whether he/she is getting closer or farther away from the destination target.  Think of them as a visual way of playing "hot or cold".  The servo will open the lockbox.  Estimated costs:  $25 for Arduino Micro + $15 for Keypad + $6 for LCD + $13 for I2C GPIO + $12 for servo + $40 for GPS =~ $80 + consumables.

The current setup uses every single pin of the Arduino Micro.  Check out the pins assignments:

I started with getting the GPS modem working.  There was lots of great help online for doing this, and I want to credit these two websites for getting me all set: http://letsmakerobots.com/node/34050 and http://forum.arduino.cc/index.php?topic=7490.0.  The first showed me how to hookup the GPS using a softserial interface.  Apparently for this modem a normal hardware serial interface doesn't always work, but the software serial port seemed to do the trick.  It also took me a while to realize or find out that SoftSerial only works on certain pins... this problem was a recurrent one as I switched around pins or migrated to a different Arduino board.  For my original setup, I was using a 1280 Mega, and found out that the "Not all pins on the Mega and Mega 2560 support change interrupts, so only the following can be used for RX: 10, 11, 12, 13, 14, 15, 50, 51, 52, 53, A8 (62), A9 (63), A10 (64), A11 (65), A12 (66), A13 (67), A14 (68), A15 (69)" - Arduino SoftSerial Documentation.  Once I used the appropriate pins, and a simple program to setup the SoftSerial connection and then read in the available data, I was seeing a stream of text from the GPS.

#include <SoftwareSerial.h>
SoftwareSerial mySerial(2, 3, false);
void setup()  
void loop()
{if (mySerial.available())

The next step was to take the serial input data and parse it into useful information.  The MediaTek datasheet shows that the GPS outputs data in several modes (GGA, GSA, GSV, RMC, VTG) and I was seeing parts of all of those modes.  However, the mode that I was consistently getting a full string from was GGA.  I could tell because I could see the string starting with $GPGGA and ending with *OF or some similar two hexadecimal checksum starting with '*'.  So I decided to parse that string.  My savior was codename GeckoCH from the second site listed above, who posted code to parse a GPGMC type string.  I was able to adapt this code to read in serial data until it found "$GPGGA", then pull the time, latitude, longitude, and altitude.  I have not implemented a checksum check because the datasheet does not show how the checksum is calculated.

The function readline pulls in serial data until a buffer overflow would occur (bad) or a carriage return is seen (good).  According to the GPS datasheet, a carriage return is the end of each sequence.

void readline(void) {
 char c;
  bufferIndex = 0;
  while (1) {
      c=mySerial.read();    // read a byte
      if (c == -1)          // if the byte is -1 don't record and skip to the next byte
      if (c == '\n')        // if the byte is newline don't record and skip to the next byte
      if ((bufferIndex == BUFFERSIZE-1) || (c == '\r')) {    // if buffer overflow OR byte is a carriage return, reset the buffer and quit readline
        buffer[bufferIndex] = 0;
      buffer[bufferIndex++]= c;  // otherwise store the byte

The only problem with this function is that if the GPS is sending nothing, this function doesn't break out of the infinite loop.  That's a bug to be fixed later.  Next in the main loop we look for the first six characters of the buffer to match the string $GPGGA.
if (strncmp(buffer, "$GPGGA",6) == 0)  // Parse the string when it begins with $GPGGA

This is somewhat confusing since when we look at the buffer, the first six characters are never $GPGGA; they are other parts of the string from the modem.  But I think somehow the buffer gets shifted to the left each cycle so that at some point the string $GPGGA ends up at the front.   Anyhow, it works.  When that happens (and only when that happens), we parse the string looking for deliminators like decimals and commas, and pull out the appropriate data between those deliminators.  For example, the code below finds the longitude by looking after a comma.  It skips the decimal (because floating point math is notoriously bad) then adds in the numbers after the decimal.

// longitude
    parseptr = strchr(parseptr, ',')+1;
    longitude = parsedecimal(parseptr);
    if (longitude != 0) {
      longitude *= 10000;
      parseptr = strchr(parseptr, '.')+1;
      longitude += parsedecimal(parseptr);

uint32_t parsedecimal(char *str) {
  uint32_t d = 0;
  while (str[0] != 0) {
   if ((str[0] > '9') || (str[0] < '0'))
     return d;
   d *= 10;
   d += str[0] - '0';
  return d;

GPS data comes in at least three formats that I have seen.  The first is a straight decimal.  For example, the location of the school that I work at is latitude: 47.6129121, longitude: -122.3161762 (I found this using this cool website for which you can enter an address and find the GPS coordinates).  Latitude is a degree between -90 and 90, where 0 is the equator, the northern hemisphere is positive numbers up the to north pole (90), and the southern hemisphere is negative numbers down to the south pole (-90).  Longitude is measured relative to the prime meridian and goes from -180 to 180 with negative numbers for longitudes west of the prime meridian and positive numbers east of it.

The second and third formats are Degrees, Minutes, Seconds and Degrees, Minutes.  In the latter format, my school's address is N47° 36.7747', W122° 18.9706' .  For more details on going back and forth between these types, see http://en.wikipedia.org/wiki/Geographic_coordinate_conversion.   According to the Mediatek datasheet, the GPS output is in the format ddmm.mmmm for latitude and dddmm.mmmm for longitude.  This means the first two/three numbers are degrees, and the last 6 are minutes with a decimal.  The raw output at my school is shown below, which we can interpret to mean N47° 36.7557', W122° 18.929' . This is pretty close, although not perfectly aligned with the online coordinates.

It will be useful for me to convert from the GPS output (eg ddmm.mmmm) to a decimal because I will be wanting to calculate the difference between the current location and the destination location.  This conversion can be done (using floating point math for now...may need to change is precision suffers) by using the equation DD = D + M/60.  So, for example, if ddmm.mmmm = 4736.7557, then DD = 47 + 36.7557/60 = 47.6125. More on that in a subsequent post.

The test code I downloaded (from codename GeckoCH) prints out latitude and longitude in Degrees, Minutes, Seconds format using the integer and mod trickery below (note that previously the raw value from the GPS modem was multiplied by 10,000 to eliminate the decimal.

      Serial.print(latitude/1000000, DEC); Serial.print("\260"); Serial.print(' ');
      Serial.print((latitude/10000)%100, DEC); Serial.print('\''); Serial.print(' ');
      Serial.print((latitude%10000)*6/1000, DEC); Serial.print('.');
      Serial.print(((latitude%10000)*6/10)%100, DEC); Serial.println('\"');

The output from the computer's serial monitor looks like this:

I would like the final lockbox to be a stand-along unit which doesn't require a computer.  Therefore, I needed to add a keypad to put in data, and an LCD to display data.  The LCD gave me serious trouble at first; displaying letters only in uppercase or lowercase, and displaying gibberish when I tried to print certain characters (such as a space or !) to the screen.  I had some luck troubleshooting this behavior using test code found from this website:  http://www.microcontroller-project.com/displaying-ascii-characters-on-16x2-lcd-with-ardunio.html which helped me to see that the LCD was displaying only the numbers 0-9, the letters A B C D E, the remainder of the alphabet in lowercase, < > = .  Using the code below, it cycled through this sequence several times before cycling through a set of what looked like chinese characters.

void loop()  {
  int count=33;
  char ascii=0x00+33;
  while(count!=235) {
    lcd.setCursor(0, 0);
    lcd.print("DECIMAL = ");
    lcd.setCursor(0 , 1);
    lcd.print("ASCII = ");
    lcd.clear();  }

When I migrated from the Arduino Mega to the Arduino Micro and used a different LCD with my previously mentioned PCB board, the problem disappeared.  I haven't tested the original LCD to see if the problem was with it or the wiring to the Mega.

The keypad is being read from using a GPIO board.  I initially tried the wiring and code displayed on their website: http://www.dfrobot.com/wiki/index.php/Arduino_I2C_to_GPIO_Module_%28SKU:DFR0013%29 which failed to produce output which varied at all.  In fact the sample code just returned 69 (ascii for 'E') regardless of what button was pressed.    In looking at the code, you can get a sense of what it does.  The function below first toggles one of the output pins; the outer for loop does this, setting 0.7 (10000000) high and the other three outputs low, then bit shifting (input = input >> 1) so that 0.6 (01000000) is high, and so on.  While one of the columns is high, it then scans the inputs looking for 1000 (0x8), then 0100, 0010, and 0001.  If it finds a match, it records the values of i and j (which correspond to the column and row of the key pressed) and then calls another function (outputchar) which just returns the key pressed.

unsigned char readdata(void)          //main read function
  int input=128;                      //binary 10000000, set pin 0.7 HIGH
  for (int i=0;i<4;i++)               //for loop
    write_io (OUT_P0, input);      
    unsigned int temp=0x8;            //temporary integer, binary 1000, to compare with gpio_read() function  
    for (int j=0;j<4;j++)
         if (gpio_read(PCA9555)==temp)
         { return outputchar(i,j);}   // output the char
         temp=temp>>1 ;               // shift right
    input=input>>1;                   //shift right, set the next pin HIGH, set previous one LOW
  return 'E';                         // if no button is pressed, return E

I tried several troubleshooting techniques.  The first was to use a 5V supply to apply voltage to each of the columns, then press and read the rows using a multimeter.  Sure enough, only when the correct button was pressed did I see 5V.  This told me that the keypad was working fine.   Second, I tried to apply 5V directly to one of the inputs.  I reasoned that this should at least trigger an output other than 'E'.  It did not.    Next I simplified the code, just having it print the value of the 4 input pins (eg print the value of gpio_read(PCA9555)).  I noticed that the output was always 15 (decimal for b1111), which meant that the inputs were pulled high.  This suggested there were pullup resistors on the inputs.

 See http://www.francisshanahan.com/index.php/2009/what-are-pull-up-and-pull-down-resistors/ from which the picture at left is taken.  With pullup resistors, you want to ground the input and look for a low signal.  In the picture at left, when the switch is closed, the input sees GND (digital 0).  With this in mind, I modified the readdata function to look for one of the inputs to be LOW (eg 0111 or 1011 or 1101 or 1110).  One way to code this is to use the code below:

unsigned int temp=0x0F;   //binary 1111
temp ^= (1 << j);  

The ^= symbol is an XOR logic, or exclusive OR.  It compares the two inputs, and if ONLY ONE of the inputs is a 1, it returns a 1.  So if both of the inputs are 1 or both of the inputs are 0, it returns a 0.  In this case, we XOR 1111 with 0001 (when j = 0).  This returns 1110, since the first three bits were a 1 XORed with 0 (giving 1), but the last bit was a 1 XORed with a 1 (giving 0).  Basically, this a tricky way to turn OFF the bit in the "jth" position.   With this code substituted into the inner for loop, I finally saw changes in the output from 15 (1111) to 14 (1110) or 13 (1101) or 11 (1011) or 7 (0111) when buttons were pressed.  However, it didn't matter which row was being pressed.  This suggested that something was still wrong, and that the problem was with how I was setting the outputs.

I guessed that maybe I should be setting only one of the outputs LOW, instead of setting only one of the outputs HIGH, and low and behold, it suddenly all worked.  A wonderful moment.  In retrospect, this is obvious.  Instead of setting one line HIGH and then reading all the outputs for the line looking for one to be HIGH, if the default is already HIGH, we should set one line LOW then read all the outputs looking for one to be LOW.  The updated code which works is shown below:

unsigned char readdata(void)          //main read function
  for (int i=0;i<4;i++)               //for loop
    int input=0xF0; 
    input ^= (1 << (i+4));            // toggle output LOW (outputs are 5th - 8th bits)
    write_io (OUT_P0, input);    
    for (int j=0;j<4;j++)
         unsigned int temp=0x0F;   //binary 1111
         temp ^= (1 << j);  
         if (gpio_read(PCA9555)==temp)
         { return outputchar(i,j);}   // output the char
  return 'E';                         // if no button is pressed, return E

The last piece of the hardware puzzle was the RGB LEDs.  I found this very helpful instructable:  http://www.instructables.com/id/RGB-LED-Tutorial-using-an-Arduino-RGBL/ and basically worked off of that.  One test of the analog code (which fades between colors) sold me on the fact that using digital outputs is not very cool.  This is because the digital outputs only turn ON or OFF each of the Red, Green, and Blue LEDs, allowing only certain colors.  If you use a PWM output for each of the colors, you can vary the intensity of each color, allowing basically all the possibly colors.  So much cooler.  Not clear on what I mean?   Watch these two videos and tell me which is cooler :)

https://www.youtube.com/watch?v=TwoXCqJAtLw  (Digital)
https://www.youtube.com/watch?v=SbdazwSjIOU (Analog)

Here's a video of my actual RGB LEDs:

The goal here will be to have the rate of fading depend on how close the user is to the GPS target... speeding up as he/she gets closer.  I've got a little work to do, since the fading code from the instructable is synchronous (eg it uses delay functions to dictate the speed of the code).  I need the LEDs to change colors asynchronously so that the communicates with the GPS can continue to happen.  This shouldn't be too hard by using comparisons with the timers rather than delays.

Current version of the code can be found on GitHub:  https://github.com/gcronin/GPSLockbox/blob/master/GPS_micro/GPS_micro.ino .

Friday, April 11, 2014

PWM 555 Timer Servo and DC Motor Tester

For robotics, my students use servos and Tetrix motors to actuate mechanisms.  Sometimes programming is not their forte or they don't want to take the time to wire up a motor/servo controller to an NXT and 12V battery and write basic code... they just want the servo or motor to run so they can get a quick feel for if a prototype will work.

I put together two testers which allow them to quickly hook up a servo or a motor and test it.  The testers allow full range of servo motion and full range of motor speeds from full forward to full reverse.  Both of the testers use exactly the same circuity; a 555 timer with a variable resistor using a potentiometer to set the pulse width.  For the servo tester, this PWM signal directly controls the position of the servo.  For the motor tester, the PWM signal is sent to a Parallax HB-25 Motor Controller (not cheap, but I had a few lying around already).  The video below shows an oscilloscope capturing a PWM pulse output from an Arduino.  You can see the width of the pulse varying over time.

The schematic for the servo tester is shown at left.  R1 is  ~250k plus the 20k potentiometer from one pin to the sweep (eg 250k - 270k).  R2 is 12k plus the rest of the potentiometer (eg 32k - 12k).  The capacitor is 0.1 uF.  Using the equations for the 555 timer, we have a range of outputs of T1 = 19500us, T2 = 830us up to 2200us.  The T2 time is what we want to pulse to the servo, but in the configuration given, T2 is the time spent LOW.  To reverse the polarity of T1 (making that LOW) and T2 (making it high), the output from the 555 is run through an NPN transistor which acts as a NOT gate.  The results are shown below:

A breadboard prototype worked fine, so I used Eagle CAD to make boards for each of the testers.  The EagleCAD files can be downloaded from https://sites.google.com/site/rampantrobots/servotester .

The boards for the servo and motor tester are almost identical, except that the servo requires a voltage of ~5V, and the motor requires a voltage of 12V.  I wanted both to run off the 12V batteries we use for robotics.  So I used a LM7805 for both boards.  For the servo board, the 5V output also powers the servo, while for the motor board the 12V supply is connected directly to the HB-25 input.  For the servo tester, a series of capacitors of different values is used to smooth the 5V line because the servo pull might otherwise cause it to drop too low for the 555 timer to work correctly.  The bottoms of each board show the similarities and differences.

The boards work great.  Check out the movies below which show them in action.