Assignment 4: Dot Dot Dot, Dash Dash Dash, Dot Dot Dot

Click here to access the respository for this assignment.

The idea

In this assignment you’ll use the combined power of Java and Arduino’s communications. Java will send data to the Arduino, and the Arduino will process those bytes and display their contents in Morse code. Morse code is an example of a substitution cipher because individual letters are replaced by a specific, fixed Morse code.

By the end of this assignment you should have a better understanding of how data streams work and how they can be used. In addition, you will practice: iterating through arrays, working with ASCII characters, and considering the logic required to properly implement non-blocking (i.e., delta) timing.

The background

The Java side of the communication

We will be using the Java SerialComm class, which you created during the last studio. If you don’t yet have a functionally complete SerialComm class (that supports debug of outgoing bytes), you will want to finish that first.

The Arduino

Serial stream

The primary functions for interacting with Arduino’s incoming Serial data are Serial.available() and Serial.read(). The Serial object operations are also built on top of Stream objects.

Serial.available() returns the number of bytes currently available to the Serial.read() function. Serial.read() reads the next byte from the stream.

Additional Functions

You will also use morseEncode(char symbol), which has already been written for you. morseEncode() will return a String that contains data corresponding to the Morse code for a given symbol. Since Morse code doesn’t have codes for lowercase letters, you will author a toUpper() function that will convert lowercase characters to uppercase. This will enable the morseEncode() function (which already calls toUpper(), meaning you don’t have to edit morseEncode()) to translate the alphanumeric characters inputted to morse code. toUpper() should have the following parameters:

// Argument: Any character
// Return Value: Either:
//    1) If the character is a letter, 
//       the upper case equivalent.  
//    2) If the character is not a letter, 
//       the original value.
char toUpper(char c) {
    ...
}

The easiest way to complete toUpper() is to use the properties of ASCII values. If you look closely at the ASCII Table you’ll notice that a lower case letter can be converted to an upper case letter by changing just one bit, so you may want to review bitwise operations. Since the letters are numeric and in lexicographic order they can also be compared via the normal relational operators, like:

char c;
...
// Does this character come "at or after" 
// the character for 0? (The character 1 would 
/  come after 0, etc.)
if(c>='0') { 

Using these properties of ASCII (being able to do comparisons, lexicographic ordering, and being able to manipulate bits) you can complete toUpper() with just a few lines of code.

Including files

There are three files in the MorseCoder/ directory:

  • MorseCoder.ino: This contains a nearly empty sketch. You will complete all your Arduino work here.
  • MorseCodes.h: This is called a header file (hence the “.h” extension) and contains things that need to read at the top (or head) of a source file to define features used later in the file. The MorseCoder.ino Arduino file “includes” it to have access to the function defined in MorseCodes.cpp. A header file and corresponding #include are used somewhat like the import statement in Java. Read through the comments which describe the function(s) you may need to use for this assignment.
  • MorseCodes.cpp: This contains a Morse code table and code that uses it. You do not need to modify it. Although you don’t need to understand the exact details of how morseEncode() works to be able to use it, take a few minutes and read through it. You should notice that it uses the properties of ASCII in a few ways.

Header files are an important part of code organization in C. They’re usually paired with a source file (Often with a “.c”, “.cc”, or “.cpp” extension). This separation helps for several reasons: it speeds up compile time, keeps code organized, and it can prevent errors due to order of declarations. Most importantly, the header is usually used to specify a public interface to functions whereas the corresponding source file provides private declarations. Often when you just need to use a function and don’t care about how it works you can simply refer to the header file.

Often a library of related functionality is contained in one source file (.cpp file) that may be used in many different projects. The MorseCodes files are a library that can be used to encode and decode Morse code.

The Arduino Assignment

Getting the Arduino to recognize input from the Serial Monitor

  1. Start by making the sketch in MorseCoder.ino read in characters from the Serial Monitor and echo them back, as in the studio.

  2. Complete the toUpper() function and test it. Use it to ensure all the echoed characters are in upper case. For example, if the following is entered:
        Don't shout at me! 

    It would be echoed back as:

        DON'T SHOUT AT ME!
  3. Use the morseEncode() function to echo back the Morse code of the characters instead of the characters themselves. morseEncode() will use your toUpper() function, so there’s no need for you to call toUpper() yourself. Read the comments in MorseCodes.h for details of how you need to call morseEncode().

  4. Update your code to blink an LED for the given Morse code. You will have to process each character of the Morse code separately. For example, the morseEncode() function will return a String. You will have to loop through the returned string, retrieve each character, and use the character to turn on an LED for the appropriate amount of time. You can access an individual letter by using array-like notation. For example, this would print each letter in a string one-at-a-time:
String words = "Hello World!";
for(int i=0;i<words.length();i++) {
    Serial.print(words[i]);
}
  1. The timing of Morse code should be as follows:
    • you chose the duration of 1 unit; 500 ms is a good choice
    • a dot, ., turns the LED on for 1 unit
    • a dash, -, turns the LED on for 3 units
    • a space ensures the LED is off for a total of 7 units
    • between the dots and dashes of a single symbol’s code, turn the LED off for 1 unit. For example, the code for A is .-. The LED needs to be off for 1 unit between when it is on for the . and then on again for the -.
    • between symbols the LED should be off for 3 units. For example, if coding two letters, like AB you would have to blink the code for A, which is ._, and the code for B, which is -.... The LED should be off for three units of time between the final symbol of A and the start of B to indicate the end of a letter.
    • when not displaying a dot or a dash the LED should be turned off.

Here are two visualizations showing the full encoding of OH HELLO:


Time:       1234567890123456789012345678901234567890123456789012345678901234567890123
Message:    O----------   H----       H----   E   L--------   L--------   O----------
Signal:     HHHLHHHLHHHLLLHLHLHLLLLLLLHLHLHLLLHLLLHLHHHLHLHLLLHLHHHLHLHLLLHHHLHHHLHHH
             ^     ^      ^       ^             ^
            dash   |     dot      |             |
             symbol space     word space  letter space
Character Complete Morse Code Each Symbol LED On Time LED Off Time
O --- - (1st -) 3 1
  - (2nd -) 3 1
  - (3rd -) 3 1
(between letter)       2 (total of 3 including above)
H .... . (1st) 1 1
  . (2nd) 1 1
  . (3rd) 1 1
(between letter)       2 (total of 3 including above)
(space)       4 (total of 7 including above)
H ... . (1st) 1 1
  . (2nd) 1 1
  . (3rd) 1 1
(between letter)       2 (total of 3 including above)
E . . 1 1
(between letter)       2 (total of 3 including above)
L .-.. . 1 1
  - 3 1
  . 1 1
  . 1 1
(between letter)       2 (total of 3 including above)
L .-.. . 1 1
  - 3 1
  . 1 1
  . 1 1
(between letter)       2 (total of 3 including above)
O --- - (1st -) 3 1
  - (2nd -) 3 1
  - (3rd -) 3 1

Although you may initially use delay() timing to get going with the assignment, when you’re done you should only be using delta timing (i.e., NO delay() allowed).

The PC Assignment

Your final task is to author a Java program that performs very close to the same function as the Serial Monitor.

  1. Complete the PCToMorseCode in src/assignment5. It will be somewhat like your HexTX from Studio 5 with a few minor changes:
    • It will read an entire line at a time.
    • It will send send each character to serial port.
    • The debug in SerialComm should be set to true so that you can see the bytes as they go into and out of the Java program (to/from the Arduino).

Demo

The following video shows a brief demo of the expected timing:

  • The pane on the right shows the text being entered on the console by the user
  • The horizontal band shows the Morse code being typed out. The “|” indicates the time. The red one is the current time.
  • Immediately above the | is the current symbol being sent. A dot takes a single time period and a dash takes three time periods (so a dash is over three |s). Note the time between dots/dashes of a single letter (one |), between different letters of the same word (a total of three |s), and between different words (a total of seven |s).
  • Above the dot/dash indicators is the specific letter being sent.
  • The light is in the lower right corner. Note that for a dash it is lit for three |s and for a dot it is lit for a single |.

The check-in

Verify that you have completed the following files:

- `MorseCoder/`
	- `MorseCoder.ino`
	- `MorseCodes.cpp`
	- `MorseCodes.h`
- `communication/`
	- `SerialComm.java`
	- `PCToMorseCode.java`
  1. Commit all your code. Do not ask to be checked out by a TA until after you have made certain that your work is committed. Failing to do this may result in you losing points on your assignment.

  2. Follow the checklist below to see if you have everything done before demo your assignment to a TA.

    • Your sketch is able to read input from Java or the Serial Monitor
    • Serial.read() should only be invoked when data is available()
    • LED flashes properly (dot and dash are distinguishable)
    • Timing between letters and between words is correct
    • No delay() is used in your work
    • Your Java program reads lines of characters and sends them through the serial port.
    • Your Java program uses debug to display the outgoing data
    • All of your files are committed
  3. Check out with a TA.

The rubric

  • 100pts: Did the demoed assignment work?
    • toUpper() meets requirements (10pts)
    • Proper Morse code. Dashes differ from dots, blinking the correct patterns. (15 pts)
    • Proper timing between dots/dashes, between symbols, and between words (15 pts)
    • Arduino code uses delta timing (No delay()s) (15 pts)
    • Arduino code is well-organized, variables are appropriately named, and comments are used to aid understanding (10 pts)
    • Java code reads from the console and sends data to the Arduino (25 pts)
    • The SerialComm class has an appropriate writeByte() method (10 pts)