Saturday, September 24, 2016

Rover2: Serial Motor Controller

My new rover is supposed to be simpler than the first one and something that irked me with the first design was the motor controllers.  They worked, but they are ridiculously complicated.  So I bought a Sparkfun Monster Moto Controller and hooked it up - much simpler.

The only hassles with it is that if you would plug another board on top of it, then it could short to the tops of the capacitors and the VIN connector could short to the Arduino ICS pins underneath it.  I stuck a rectangle of clear plastic cut from some screws packaging between the boards and snipped the ICS pins off - done.

Serial Control

Controlling a DC motor is straight forward, using two pins to switch the H bridge direction (INA1, INB1) and one for speed PWM (PWM1).  There is also a current sense input (CS1) that you can set to turn the motors off if they get stuck and the current increases too much.  You'll have to set the sense level with trial and terror.

Here is an example for a serial motor control interface, using simple two character messages:
  • ms - stop
  • mf - forward
  • mb - backward
  • mr - turn right
  • ml - turn left
  • mu - speed up
  • md - slow down
This is also a good example of how to parse a serial data protocol efficiently with a switch-case statement in C.  A switch statement is easy to read and understand by humans and very efficient on computers.  Each case compiles to a check and a conditional jump.  It doesn't slog through all the code from top to bottom.

// Monster Moto Board
// Herman Oosthuysen, Sep 2016

// Monster motor controller with simple two character ASCII serial interface
// Controls two motors to make a rover run forward reverse and turn
// Turns are executed by speeding up the motor on one side and slowing down (or reversing) on other side
// A command starts with M and ends with Enter (CR or LF)
// MS - Stop, short the motors to ground
// MF - Forward, best to slow down and stop before going backward
// MB - Backward, best to slow down and stop before going forward
// ML - Turn Left, cancel a left turn with a right turn
// MR - Turn Right, cancel a right turn with a left turn
// MU - Speed Up, only three speed steps, stop, slow and fast
// MD - Slow Down, only three speed steps, stop, slow and fast

// Literals
#define BAUD  9600
#define RATE  200
#define CR    0x0D
#define LF    0x0A
#define MAX   2
#define MIN   -2
#define INC   0x20
#define MAXCS 0x80

// Pins
// Motor 1
#define INA1  7
#define INB1  8
#define PWM1  5
#define EN1   A0
#define CS1   A2

// Motor 2
#define INA2  4
#define INB2  9
#define PWM2  6
#define EN2   A1
#define CS2   A3

// Global Variables
int cs1 = 0;  // 0 to FF
int cs2 = 0;  // 0 to FF
int spd1 = 0; // -2 to 2
int spd2 = 0; // -2 to 2
int pwm1 = 0; // 0 to 1023
int pwm2 = 0; // 0 to 1023
char ch = 0;  // ASCII character received
char adr = 0; // ASCII M start of Motor message
char cmd = 0; // ASCII command


void setup()
{
  Serial.begin(BAUD);
  Serial.println("Monster Moto, eh.");
 
  // Stop the motors
  pinMode(INA1, OUTPUT);
  digitalWrite(INA1, LOW);
 
  pinMode(INA2, OUTPUT);
  digitalWrite(INA2, LOW);
 
  pinMode(INB1, OUTPUT);
  digitalWrite(INB1, LOW);
 
  pinMode(INB2, OUTPUT);
  digitalWrite(INB2, LOW);

  // PWM zero speed
  analogWrite(PWM1, 0);
  analogWrite(PWM2, 0);
}


void loop()
{
  if(Serial.available())
  { 
    ch = Serial.read();

    // Message starts with M and ends with Enter
    // eg: ms[enter]
    // Ensure that the serial terminal sends the line ends
    if((ch == CR) | (ch == LF))
    {
      adr = 0;
      cmd = 0;
    }
    else if((ch == 'M') | (ch == 'm'))
    {
      adr = 1;
      cmd = 'M';
    }
    else if(adr)
    {
      cmd = ch;
      Serial.print("cmd = ");

        switch(cmd)
        {
          case 'S':
          case 's':
            spd1 = 0;
            spd2 = 0;
            pwm1 = 0;
            pwm2 = 0;
            Serial.println(cmd);
            break;
   
          case 'F':
          case 'f':
            spd1 = 1;
            spd2 = 1;
            pwm1 = spd1 * INC;
            pwm2 = pwm1;
            Serial.println(cmd);
            break;
   
          case 'B':
          case 'b':
            spd1 = -1;
            spd2 = -1;
            pwm1 = abs(spd1) * INC;
            pwm2 = pwm1;
            Serial.println(cmd);
            break;
   
          case 'L':
          case 'l':
            spd1--;
            if(spd1 < MIN)
              spd1 = MIN;
            spd2++;
            if(spd2 > MAX)
              spd2 = MAX;
            pwm1 = abs(spd1) * INC;   
            pwm2 = abs(spd2) * INC;  
            Serial.println(cmd);
            break;
   
          case 'R':
          case 'r':
            spd1++;
            if(spd1 > MAX)
              spd1 = MAX;
            spd2--;
            if(spd2 < MIN)
              spd2 = MIN;
            pwm1 = abs(spd1) * INC;
            pwm2 = abs(spd2) * INC;        
            Serial.println(cmd);
            break;
   
          case 'U':
          case 'u':
            spd1++;
            if(spd1 > MAX)
              spd1 = MAX;
            spd2++;
            if(spd2 > MAX)
              spd2 = MAX;
            pwm1 = abs(spd1) * INC;
            pwm2 = abs(spd2) * INC;       
            Serial.println(cmd);
            break;
   
          case 'D':
          case 'd':
            spd1--;
            if(spd1 < MIN)
              spd1 = MIN;
            spd2--;
            if(spd2 < MIN)
              spd2 = MIN;
            pwm1 = abs(spd1) * INC; 
            pwm2 = abs(spd2) * INC;       
            Serial.println(cmd);
            break;
   
          default:
            Serial.println();
            Serial.print("Err = ");
            Serial.println(cmd);
            break;
        }
        Serial.print("spd1 = ");
        Serial.println(spd1);
        Serial.print("spd2 = ");
        Serial.println(spd2);
        Serial.print("pwm1 = ");
        Serial.println(pwm1);
        Serial.print("pwm2 = ");
        Serial.println(pwm2);  
    }
  }

  // Periodic Motor Control Update
  sense();
  direction(spd1, spd2);
  speed(pwm1, pwm2);
  delay(RATE);
}


// Left and right hand motors rotate in opposite directions
void direction(int spd1, int spd2)
{
  if(spd1 >= 0)
  {
    digitalWrite(INA1, HIGH);  // CW - Forward
    digitalWrite(INB1, LOW);
  }
  else
  {
    digitalWrite(INA1, LOW);  // CCW - Reverse
    digitalWrite(INB1, HIGH);         
  }
 
  if(spd2 >= 0)
  {
    digitalWrite(INA2, LOW);  // CCW - Forward
    digitalWrite(INB2, HIGH);
  }
  else
  {
    digitalWrite(INA2, HIGH);  // CW - Reverse
    digitalWrite(INB2, LOW);         
  }
}


void speed(int pwm1, int pwm2)
{
  analogWrite(PWM1, pwm1);
  analogWrite(PWM2, pwm2);
}


void sense(void)
{
  cs1 = analogRead(CS1);
  cs2 = analogRead(CS2);

  if((cs1 > MAXCS) | (cs2 > MAXCS))
  {
    spd1 = 0;
    spd2 = 0;
  }
}


Seriously High Power

MOSFETs can be paralleled, so if you need to control a very big motor, then you can wire the two channels together, so this is a really nice controller board that could control a winch, a scooter or a wheel chair for example.  So it would be good if you need to build something to help a disabled friend - Sparkfun to the rescue!

A high powered motor controller should be close to the motor because of the high currents in the wires.  So in a big system, you may need one Arduino per motor controller.  If you use the same serial interface protocol on multiple Arduino powered actuators, then you can multi-drop the serial line from the main control computer to the various actuator computers.  For this example, the messages start with M and for something else, it could start with S or whatever else you like.

For a simple toy, error checks are a waste of time, so I just send a message and hope it works.  I don't bother with CRCs and Retries on toys, but if you want to control a winch or a wheel chair, then you should be more careful!

BTW, if you need to build something serious operating at 12V, then I recommend that you get Anti-Gravity batteries.   These are light weight and will ensure that one can still manhandle the thing - lead-acid batteries make it impossible to lift a wheel-chair into a car.

 
La Voila!

Herman

Saturday, September 3, 2016

DSP on an Embedded Processor

Doing digital signal processing on a teeny weeny Arduino processor requires some trade-offs, since it is slow and doesn't have much memory.  However, bear in mind that today's embedded processors are faster than yesteryear's DSPs, so all you need to do, is use yesteryear's methods!

What it mostly amounts to, is careful use of integers and shifts, instead of floating point numbers and multiplies.  If you can, limit multiplies, divides and buffer sizes to powers of 2.  That affords enormous speed optimizations.

Circular Buffers

For example, let's filter input from an 8 or 10 bit A/D on a little 16 bit embedded processor.  This usually requires a low pass filter.  A simple low pass filter is a moving average and to do that, you need to keep a buffer of old data values.

If you are smart, then you will set up a circular buffer with 10 values, but if you are smarter, then you will use a buffer with 8 or 16 values instead - why?

If the buffer size is a power of 2, then you can make the index wrap around automagically with a simple bit wise AND function, thus making management of the circular buffer quite trivial.

Say the data buffer size is 16, with a read and write index r and w:
unsigned int buffer[16];
unsigned int r = 0;
unsigned int w = 0;

Then you can post increment the index with w++ and make it wrap with AND 0x000F, like so:
buffer[w++] = data;
w &= 0x000F;

The index w will then wrap around to zero when it reaches 16, without the use of any complicating ifs, thens elses or buts!

Do the same thing when you read from the buffer.

How do you know when the buffer is full/empty?

Easy, when r == w, then you are in trouble and the buffer is either full or empty, depending on what you are doing.  As easy as pi...

Maintaining Precision

When doing mathematics in integers, the fractional amounts that you can lose during calculations can add up over time and cause wild inaccuracy.  You can mitigate this problem by scaling.

Simply multiply the A/D input value by 16 immediately and eventually when you output a result, divide by 16.  That provides 4 fractional bits for precision on the bottom end and you still have a few bits on the top end for overflows.

The above example then becomes:
buffer[w++] = data << 4;
w &= 0x000F;

Hanning Filter

Everybody uses some sort of moving average low pass filters, so just to be different, I'll describe a Hanning filter instead.

y[k] = (x[k] + 2x[k-1] + x[k-2]) / 4

This filter only needs 4 variables, and you can multiply with one shift and divide with two shifts:

y[k] = (x[k] + x[k-1]<<1 + x[k-2]) >>2

You can use a 4 long data buffer and rotate the index through it in a circle, same as above:

Save new data, pointer++, pointer & 0x0003
Read old data, pointer++, pointer & 0x0003
Read older data, pointer++, pointer & 0x0003

You are now ready to save new data again.

So with a little bit of head scratching you can implement a Hanning filter very efficiently.

Moving Average

A rolling mean can be calculated on the fly without a buffer:
Take 1/8 of the current input data x(k) and add it to 7/8 of the previous output data y(k-1).  
This yields the new output data y(k).

y[k] = (7 * y[k-1] + x[k]) / 8

Now how do you do that on a small processor that cannot multiply and divide efficiently?

Divide by 8 is easy:
y = x >> 3

Multiply by seven?  Multiply by 8 and subtract again
y = x << 3
y -= x

The result has similar complexity to the Hanning filter above.

GPS Position Filter

To use a GPS receiver in a toy, one needs to stabilize the received position data.  The cheap toy GPS data typically varies by +-7 meters or worse.  Considering that a typical backyard is not much bigger, this makes it hard to use GPS for navigation of a model car or airplane.

A toy car moves slowly, so you can use a heavy handed low pass filter on the latitude and longitude as above, but it really is only useful when you play in a large park and you have a large battery and good obstacle avoidance sensors, since GPS alone won't keep your toy on a pathway.

La voila!

Herman

Thursday, September 1, 2016

Pleasant Random Jingle Generator

Beeping Computer

Way back during the time of the dinosaurs, circa 1975, when one turned on a desktop computer, it would go Beep!  That fell out of favour once Microsoft figured out how to make a computer take 3 minutes to boot up, before finally being able to emit a simple beep.   However, it is still common practice to test a new little embedded controller by flashing a LED.

Music vs Noise

Now for those tinkerers who are a little more adventurous:
How about pleasant sounding random noise? 

There are two things that help to make noise sound acceptable:
  • Use a tonal scale that everyone is used to.
  • Avoid obvious dissonance.

Scales

We could use a Pythagorian scale with 7 notes per octave and perfect harmony, but then it will sound weird - like a Scottish bag-pipe and I don't have enough Scottish genes in my ears to prevent them from bleeding.

The equal tempered (logarithmic) scale of Johan Bach (Das Wohltemperirte Clavier, 1722) ), with concert pitch (1939), is used in modern pianos and synthesizers.  Everyone in the western world is used to it - except maybe the Scots - and it is easy to calculate on the fly, using the formula:
  • fn = A * 12th root of 2 ** n
where A = 440 Hz for concert pitch.

Dissonance and Consonance

According to my namesake O'l Hermann von Helmholtz, maximum dissonance occurs when a beat between two tones is 33 Hz.  So avoid that and it should be less annoying.  This is effectively what is done in musical 'chords', which are designed for best consonance.


video

My old piano teacher will spin in her grave...

Here is a simple Arduino random jingle generator door bell where I tried to exercise the above rules.

// Teensy2 LED, Serial, Muzak
// Herman Oosthuysen 2016
// To enable the debug serial port:
// Go to Tools, Port and select cu.usbmodem12341

#include <math.h>

// A pleasant sounding random noise generator
// using the equally tempered scale and a simple test to reduce dissonance.

// Helmholtz: Maximum dissonance occurs when a beat = 33 Hz
// In a chord, one should watch the 2nd, 3rd and 5th harmonics also - most power

// Concert pitch: A4 = 440Hz (55, 110, 220, 440, 880, 1760...)
// CENT = 12th root of 2
// fn = A * CENT ** n

#define A1    55
#define CENT  1.059463094359
#define SPKR  8
#define LED   11
#define BAUD  9600

#define FMIN  200
#define FMAX  1200
#define TMIN  4
#define TMAX  8

int flsh = 0;
int tim = 0;
int fold = 0;
int fnew = 0;

void setup()  
{               
  Serial.begin(BAUD);
  pinMode(LED, OUTPUT);

  Serial.println("Teensy2, Muzak, eh.");
}

void loop()                    
{
  // A Pololu IRS05A proximity switch, makes it a funky door bell

  // or pet detector/terrorizer
  prox = analogRead(A0);  

  if (prox < 500)
  { 
    digitalWrite(LED, flsh);
    flsh ^= 1;

    // Helmholtz: Max dissonant if beat = 33 Hz
    // So avoid consecutive notes that are 'too close'
    // and since 42 is the answer to everything...
    while (abs(fnew - fold) < 42)
    {
      fnew = random(FMIN, FMAX);
    }

    tim = random(TMIN, TMAX);

    tone(SPKR, fnew);
    delay(1000/tim);
    noTone(SPKR);

    fold = fnew;

  }
}

// Bach's Equal Tempered frequency calculator
// 12 intonations per octave
// A1 = 55 Hz: n=1
// A4 = 440 Hz: n=12*4
int freq(int n)
{
  double t;

  t = A1 * pow(CENT, n);
 
  return (int)t;
}



Well, that actually sounds better than most of the stuff on Nights with Alice Cooper!

Sensors

The Arduinos are very easy to interface with little sensors.  In this example, I used a proximity switch to make it into a door bell of sorts.  I actually added it as a simple way to turn the silly thing on and off while experimenting.

Similarly, one could use a Sonar or IR Range sensor and modify the tune depending on the range of someone approaching your front door.  Sonar is sensitive to wind, so it may give false alarms if you use Sonar as the main detector, but you could aim it at a tree and listen to the wind sing.

It would also be fun to make a wacky proximity sensor Xilophone with Sonar tone or rhythm control, which could lead to children bouncing around your door playing - good for Halloween:  Twick or Tweet!

Pseudo Polyphonic

I can leave this toy running for a couple minutes, without getting annoyed by it - bored yes - but it isn't too grating on the ears, which was the whole intent of the exercise.

The weird thing is that while the program is obviously monophonic, it sounds somehow polyphonic, probably because the program also changes the metrum of the tones, which the brain then interprets as two or three melodies playing simultaneously.

I have not encountered anything in the literature describing this pseudo polyphonic effect.  Maybe it is indeed a new discovery.  It sounds monophonic when I slow it down only.

Music is not simple applied mathematics, it is psychological too.

If you are interested in computer generated music and want to be wowed beyond belief, then install a MIDI plugin in your browser and go the Wolfram Tones web site.  Dr Wolfram, is the creator of Mathematica - a real genius.  His music generator is based on Cellular Automata.  Others have used Fractals to much the same effect.

Elevator Muzak

Please just don't install this muzak generator in a 100 floor elevator, eh...

Have fun,

Herman

Saturday, August 27, 2016

Arduino Rover #2

My second rover is coming together.  The advantage of a ground rover is that it cannot fall out of the sky, so one tends to get rather more hours of amusement out of it than from a helicopter or fixed wing toy aircraft.

The first rover worked, but it was too complicated.  The problem with all toy projects is that I tend to forget what I was doing with it and I like to 'work' on multiple things at the same time.  My shop currently has a glider, a valve guitar amplifier, a VU meter, multiple radio transceivers and this rover all in various stages of incompletion.  Therefore any project needs to be modular and simple, so that I can see what is going on at a glance.  Otherwise, it ends up in a corner, gathering dust, rather sooner than later.

Rover #2 uses the Sparkfun Arduino Redboard for its brains and it is meant to be completely autonomous.  Addition of RC makes it too complicated and hard to maintain, so I ripped all that out (and it can now go back into the 2m electric glider!).

 
I re-used the Pololu 100:1 geared DC motors, enormous wheels and high power controllers, 7.8V
NiMH batteries, forward MaxSonar and Sharp IR range finder and a reverse Vishay IR Proximity sensor.   The sensors all have analogue outputs, so they are easy to hook up to an Aurduino A0, A1 and A2.  The motor controllers are serial and hooked together onto the same SW Serial Tx port used for the GPS Logger.  I only use the serial Rx for the GPS, so I use the Tx for the two motor controllers on a free pin D7.

The two wheels on the right are on one controller and the two on the left on another.  It simply turns with brute force - like a tank.

The chassis is my trademark olde skool wooden breadboard (It is rectangular, with rounded corners!) and the only parts that are screwed, wired and glued down with any sense of permanence are the 4 wheels, since having a wheel come off during a run, is disappointing.  The motors and wheels are of course totally oversized for this little toy, but it sure looks cool, eh...

The batteries, electronics and sensors are simply stuck on with 'chewing gum' (blue Tack-It).  Most wiring are plug in, using Berg 0.1" headers.  Only the batteries have polarized connectors so I can't plug them in the wrong way around (well, I still managed to do something like that regardless).

This way, it is easy to reconfigure this hoosammawhatsit and pull it apart to reprogram or solder something, without having to break out a pile of tools.

Sensors

Three sensors should be enough for obstacle detection: IR Range finder, Sonar range finder and IR Proximity detector.

Here is some example code to read the simple sensors:

// Sharp IR Range Finder
// MaxSonar Range Finder
// Vishay proximity sensor
// Herman Oosthuysen, 2016

// IR values range between about 0x0165 with wheels touching a wall, to 0x0003 to the end of the room.
// Sonar values range between about 0x000b with wheels touching a wall, to 0x0190 to the end of the room.
// The proximity sensor shows about 0x03c0 when there is nothing and 0x003c when there is something.

void setup()
{
  Serial.begin(9600);
}

void loop()
{
  int   ir = 0;
  int   sonar = 0;
  int   prox = 0;
  char  res[5];

  ir = analogRead(A0);
  sonar = analogRead(A1);
  prox = analogRead(A2);
 
  sprintf(res, "%04x", ir);
  Serial.print("ir=");
  Serial.println(res);

  sprintf(res, "%04x", sonar);
  Serial.print("sonar=");
  Serial.println(res);

  sprintf(res, "%04x", prox);
  Serial.print("prox=");
  Serial.println(res);

  delay(1000);
}


...and here is what the whole mess looks like once things are more or less connected:


Hmm, eye drops and non-alcoholic beer - both very important debug tools...

Pololu Motor Controller Setup

The motor controller needs to be configured (RC/Serial/Potentiometer control, ASCII/binary protocol and more).  This requires special software, which of course is not available for a Mac.  Curiously, they do have both Linux and Win32 versions of the control program.

So I fired up my trusty WinXP virtual machine on Virtualbox.  When plugging the USB cable in, it comes up as an unknown, so I created a USB port filter with only a Name: Pololu, Product: 0098 and Vendor: 1FFB, while ensuring everything else is completely blank.  Then replug the cable and wind up the Windows.  The Pololu program wants to run as administrator (unsurprisingly) and install a device driver twice, each time you plug the motor controller in.  I guess regular users of Windows would be used to this nonsense.

These motors are powerful enough that the buggy could do a wheelie.  I proved that with the first version.  So I would like to add an accelerometer and get it to flip onto its hind legs.

For the Arduino interface I set it to Serial/USB, Fixed 9600 baud, Binary, No CRC and made sure that the ID for the two controllers are different (mine are 13 and 14).  Save it and hope for the best.

Vroooom!

Well, for a moment there anyway.  I plugged a motor controller in wrong and fed 7.8V into the 5V supply, blew the Arduino out of the water and it went to join the crowd in the great computer heaven in the sky.   My Mac even rebooted - but is still working - fortunately.

This is why I like playing with thermionic valves - they can take a lot of abuse - little embedded processors not so much.  So it looks like I'll be making the courier company rich, unless I order multiples of these things as spares, a handful of zener diodes and whatnot for protection, but that feels like planned waste - sigh...

Monster Moto

Eventually, I replaced the two Pololu controllers with a Monster Moto board from Sparkfun.  This way, I just have three boards plugged together and one battery pack - simpler.

The Monster Moto control is described in another post.

Arduino Rover Pins Used

The GPS board has a lot of breadboard space and places where tracks can be cut and reconnected.  To get everything connected, I had to do a little surgery with a knife on the bottom of the GPS board, and remove two 810 Ohm SMD resistors from the top of the Monster Moto board, as explained below.

A0 - (EN1 Motor remove 810R); Sonar range
A1 - (EN2 Motor remove 810R); LED range
A2 - CS1 Motor
A3 - CS2 Motor
A4 - IR proximity
A5 -

D0 RX - USB Serial; (RX GPS switch to SW)
D1 TX - USB Serial; (TX GPS switch to SW)
D2 -
D3 -
D4 - INA2 Motor
D5 - PWM1 Motor
D6 - PWM2 Motor
D7 - INA1 Motor
D8 - INB1 Motor; (SW RX GPS cut track)
D9 - INB2 Motor; (SW TX GPS cut track)
D10 - (ICSP MISO GPS cut track); SW RX GPS
D11 - (ICSP SCK GPS cut track); SW TX GPS
D12 - (ICSP MOSI GPS cut track); SW RX 9DOF Sensor
D13 - SW TX 9DOF Sensor

RST   - RST GPS

Items in brackets were cut or moved.

A Friggen Laser Beam!

Just the day after I received my packet of spare Arduino Redboards, Sparkfun announced that the Garmin LIDAR is back - really bad timing, but I just have to have one.  What is a Rover without a Friggen Laser Beam?

Wow, this toy is sure getting expensive...

Have fun,

Herman

Thursday, August 25, 2016

Arduino LCD Button Shield

The Sparkfun LCD shield works very well.  It has five buttons wired to a single analogue input, which is a neat pin saver. 

However, the example code is much too complex to my liking.  Granted, it can handle multiple simultaneous button clicks, but who on earth will ever do that?  So I made something simpler:

#include <LiquidCrystal.h>

// LCD uses D4 to D9 and A0 for buttons

LiquidCrystal lcd( 8, 9, 4, 5, 6, 7 );

void setup()
{
  lcd.begin(16, 2);
  lcd.setCursor(0, 0);
  lcd.print("C'mon click it!");
}

void loop()
{
  unsigned char key;
  unsigned int sensorValue;
 
  sensorValue = analogRead(A0);
 
  if(sensorValue < 1000)
  {
     sensorValue = analogRead(A0);
    
     lcd.setCursor(0, 1);
     lcd.print("                ");
    
     lcd.setCursor(0, 1);
     lcd.print(sensorValue);

     if ((sensorValue > 600) && (sensorValue < 620))
     {
       lcd.print(" Select");
     }

     if ((sensorValue > 840) && (sensorValue < 870))
     {
       lcd.print(" Left");
     }

     if ((sensorValue > 920) && (sensorValue < 940))
     {
       lcd.print(" Up");
     }

     if ((sensorValue > 890) && (sensorValue < 910))
     {
       lcd.print(" Down");
     }

     if ((sensorValue > 800) && (sensorValue < 830))
     {
       lcd.print(" Right");
     }

  }
}


Have fun!

Herman

Minimalist Arduino GPS Parser

The Sparkfun Redboard and other toys can be a lot of fun indeed.  What I like about the Arduinos, is that the board support packages are very good and it is very easy to intersperse regular C with the simple Sketch code.

Here is a minimalist on the fly parser for NMEA GPS data positioning that I've been playing with.  It receives and analyzes one byte at a time, so there is no delay.  You have the data the very moment it is available:

// Minimal GPS Parser
// Herman Oosthuysen, 2016

#include <string.h>
#include <SoftwareSerial.h>

// GPS serial: 9600N81
// Example GPS data capture
//$GPGSA,A,3,11,31,27,14,,,,,,,,,2.09,1.85,0.96*08
//$GPRMC,155741.000,A,2413.4311,N,05541.2983,E,1.19,45.34,240816,,,A*5D
//$GPVTG,45.34,T,,M,1.19,N,2.21,K,A*03
//$GPGGA,155742.000,2413.4313,N,05541.2985,E,1,4,1.85,263.9,M,-29.9,M,,*77

//Time, Lat, North, Long, East, Fix:
//$GPGGA,155743.000,2413.4317,N,05541.2986,E,1,

//Heading true, heading magnetic, speed knots, speed kph
//$GPVTG,45.34,T,,M,1.19,N,2.21,K

const int rxpin=8;
//const int txpin=9;
const int txpin=255; // Rx only, frees up a pin
SoftwareSerial serial_gps(rxpin, txpin);

char ch = 0;
int cnt = 0;
int csv = 0;
int fix = 0;
int res = 1;
char dat[16];
char tim[16];
char lat[16];
char lng[16];

void setup()
{
  Serial.begin(9600);
  serial_gps.begin(9600);
}

void loop()
{
  if(serial_gps.available())
  {
    ch=serial_gps.read();
    //Serial.print(ch);

    // simple parser, start with $
    if(ch == '$')
    {
      cnt = 0;
      csv = 0;
      fix = 0;
    }
    else
    {
      dat[cnt++] = ch;
     
      if(ch == ',')
      {
        dat[--cnt] = 0;
        cnt = 0;
        csv++;

        if(csv == 1)
        {
          res = strcmp(dat,"GPGGA");
          if(res == 0)
            fix = 1;
        }

        // Assume N, E
        if(fix == 1)
        {
          if(csv == 2)
            strcpy(tim, dat);
          else if(csv == 3)
            strcpy(lat, dat);
          else if(csv == 5)
            strcpy(lng, dat);
          else if(csv == 6)
          {
            Serial.print("Tim: ");
            Serial.println(tim);
            Serial.print("Lat: ");
            Serial.println(lat);
            Serial.print("Lon: ");
            Serial.println(lng);
            fix = 0;
          }
        }
      }
    }
  }
}


The result is:
Tim: 154417.000
Lat: 2413.4364
Lon: 05541.2907


Cool, now you all know exactly where I live.

In days gone by, there were phone directories, now, there is GPS.

There are Arduino GPS libraries available, but they are too complex for my liking.  For a toy, one can make some assumptions to simplify things, which saves processing cycles and memory.  I don't see the need to verify the checksum, or check whether the position is north and east - I'm not going to drive my toy car to the other side of the globe.

The Arduino software and Redboard works pretty good on my Mac, but if I unplug it from the USB port, then I have to reboot the Mac to get the USB serial port to work again when I plug it back in.  There should be a way to force the Mac to reload the driver, but I haven't gotten round to diving into the OSX weeds to figure it out yet.

Have fun!

Herman

Wednesday, August 10, 2016

Audio VU Meter

I have a bunch of Magic Eye tubes and was wondering what to do with them.  An audio VU meter with a microphone pickup could make a nice magical flickering display as I originally described here: An Angel Dancing On a Pin Head.


The example Rusky circuit works, but it needs much more gain to work with a microphone and it needs a power supply of sorts.  So, I dusted the old circuit off, hooked up a little triode as an amplifier to drive the display tube and my prototype works nicely.   Moving the resultant rat's nest from the breadboard into a proper display case is another matter though.

For a little toy like this, a pair of huge transformers will increase the cost and their bulk will detract from the whole idea, so I made a simple direct mains powered supply - shocking, eh...

A magic eye tube is a tiny cathode ray tube as in old TV sets and requires a very high operating voltage.  So I used a voltage doubler on the 220 V mains.  That ensures that the electrons have sufficient kinetic energy to penetrate surface pollutants and stimulate the ancient zinc oxide fluorescent display properly.  High voltage capacitors are costly and one 630V electrolytic could cost as much as a transformer.  Put two identical(!) 315 V caps in series - much cheaper.

The miniature triode needs a much lower voltage, so I made a simple half wave rectifier for that.  So far - fairly standard.  The triode output is envelope detected to create a negative voltage to drive the display tube gate - any small signal diode will work - a 1N4148 is cheap.  You can put a trimmer on the display tube cathode to adjust the DC bias and together with the input volume control, get the shadow to flicker properly.

The heaters require about 300 mA at 6.3 V.  So for that, I made a simple triac light dimmer circuit (test it with a light bulb and set it to minimum) with the two heaters in series (I used a quadrac - one less part).  To get the right current, install a 1 Ohm resistor in series with the tubes, measure the RMS voltage over it and tweak the trimmer - then maybe put a spot of glue/wax on it.

The fuse and chokes provide some protection and spike/triac noise filtering.   You can wind your own chokes - a few turns on a ferrite toroid or ferrite cable clamp (easier to wind, since you can wind it while it is open) is all that's needed.

As with any thermionic valve circuit, this one is dangerously 'hot' and noisy.  So when this toy is running, don't poke your fingers in the works - you will be sorry...

La voila!

Herman