Wednesday, December 9, 2009

Reading Morse Code with an Arduino

For part of my ENG 198 class, we were put into groups of 5, given an Arduino microcontroller and a $100 budget, and told what was effectively, "make something."  My group decided to make a balancing robot.  Then the parts we needed were on 2-3 weeks backorder, so we decided to go with plan B--just as soon as we could figure out what that was.  Eventually, we decided to make a box that you could tap Morse code into, and it would convert it into text and display it on a small LCD screen.



This is what happens when you use part of your budget on gears for a robot... then you build something that isn't a robot.  Anyways, ignoring the last-minute cardboard enclosure, there's a serial-enabled LCD screen at the top, a force sensor at the bottom, and the hole on the left is for a photo-resistor that adjusts the backlight brightness.  I'll post a video of it in action later along with some photos of the inside, but what I really want to talk about is a simple algorithm to interpret button presses as Morse code.  The Internet is full of examples as to how to turn dots and dashes into letters, or letters into dots and dashes, but I couldn't find anything on getting those dots and dashes in the first place.

More after the break...



The code here uses the Arduino libraries, but it's really just C/C++.  millis() returns the number of milliseconds elapsed since the Arduino was switched on, and I'll be using that for timing.  digitalRead(pin) returns the state of the digital input on the microcontroller (HIGH or LOW).  The logic goes something like this:
  1.  If the switch gets pressed, record the time it became pressed
  2. When the switch gets released, record the time it was released and this to the time it was pressed to get the length of the switch press.  
  3. If this time is less than some threshold, a dot was entered, otherwise a dash was entered.
  4.  When the switch is unpressed, compare the current time to the time the switch was released to get how long it has been unpressed.  
  5. If this time is longer than some small threshold, take all the dots and dashes recorded and translate them into a character.  Print it, clear the record of dots and dashes, and remember that we already printed a character so we don't do it again until more Morse code is entered.
  6. If the unpressed time is longer than some larger threshold, this is a break between words.  Print a space and stop checking for spaces and character breaks (steps 4-6).
  7.  If the switch gets pressed again, we can start looking for spaces and character breaks again.
  8. Goto step 1.

Here's the code.  I'll leave it to you to decide how to remember the Morse code and translate it to text, but this will get you the dots and dashes:

//The digital pin the switch is on
#define SWITCH_IN  0
//If the switch is held longer than this, it's a dash
#define DASH_LENGTH  200
//If the switch is left unpressed longer than this, it's a
//character break.  
#define BREAK_LENGTH 220
//If the switch is left unpressed longer than this, it's a space
#define SPACE_LENGTH 900

void loop()
{
 //The time the switch was last pressed
 static long onTime = 0;
 //The time the switch was last released
 static long offTime = 0;
 
 static bool lastSwitch = false;
 //Current switch state
 bool switchState = digitalRead(SWITCH_IN) == LOW;
 //Was switch pressed this loop?
 bool switchPressed = switchState && !lastSwitch;
 //Was switch released this loop?
 bool switchReleased = lastSwitch && !switchState;
 //Update last state for next loop
 lastSwitch = switchState;
 
 //Keep track of these so we don't keep printing spaces
 //over and over again:
 //Have we already printed a space?
 static bool spaceEnded = false;
 //Have we already printed a character and moved on?
 static bool breakEnded = false;
 
 
 if (switchPressed)
 {
  //Record the time the switch was pressed
  onTime = millis();
  //When the switch is pressed, reset whether character
  //breaks or spaces have already been printed.
  spaceEnded = false;
  breakEnded = false;
 }
 else
 {
  //Record the time the switch was released
  offTime = millis();
  //Compare the on and off times to get the length of
  //the switch press, then check to see whether that was
  //a dot or a dash
  long pressLength = offTime - onTime;
  if (pressLength > dashLength)
  {
   //A dash was entered
  }
  else
  {
   //A dot was entered
  }
 }
 
 //If the switch is unpressed and we haven't printed a space yet,
 //check to see if we should.
 if (!switchState && !spaceEnded)
 {
  //Compare the current time and the off time to get the time the
  //switch has been unpressed, then check to see whether we should
  //print a space or character
  long waitLength = millis() - offTime;
  if (waitLength > spaceLength)
  {
   //A space was entered
   spaceEnded = true;
  }
  else if (!breakEnded && waitLength > breakLength)
  {
   //Done entering a character.  Parse the dots/dashes and reset
   breakEnded = true;
  }
 }
}

I will post the full source code of the project later.

No comments:

Post a Comment