RPi Zero Morse Code
Hey guys! I'm back with another blog post, and this time, I've got good news! I've finally gotten Raspbian working on my Raspberry Pi Zero W, and with this I now have greater control over the GPIO header than when I foolishly attempted to baremetal the thing.
So, the first thing I did? Well, I have an LED setup to be directly connected to the header (because I don't have a bread board) and so obviously the first step is to flash the LED to show that it works. That was simple, I ran a quick Python script to flash it on and off. Then I modified that script to do it forever (which lead to a bug in the code). Turns out, Raspbian comes with a Python module for GPIO control. Well, I like working in C++, so then came the challenge.
How to control the GPIO header with C++? Raspbian doesn't have a built-in library for that, but it does come with a custom library called wiringPi, which integrates similar controls to an Arduino board. So, I got the LED blinking and had to come up with a new trick. Now, when I was going baremetal I wanted to integrate some sort of Morse code system onto the board. That meant I needed a UI, keyboard interrupts, and, obviously, a keyboard. Well, thanks to installing Raspbian I no longer need to worry about the low level crap that would've taken me years to develop because Broadcom won't release their board documentation (long rant on that one...).
Well, with the basics setup I got to work (I actually wrote this post and the code at the same time). First thing I needed to do was string parsing. Well, technically first thing to do was create an infinite loop that takes in user input and has some way to exit. Anyway, once I did that it was a matter of parsing the user input into written Morse code, then parsing the code and manipulating GPIO pins.
At first, there wasn't much abstraction. I used pin 18 (code 1 with wiringPi) with an LED attached. A little bit into the code I realised that I wanted some way to control any pin, so next I changed some function names and implemented one layer of abstraction, setting a pin number. Next, I realised that Morse code is usually handled through sound, not light, so I wanted to implement a handler for that. Well, that level of abstraction was easy. Just set a pin to high for the designated amount of time then set it low. With a speaker hooked up, it should work (I have no way to test this). That's all the theory I needed, so I got back to coding the first parser.
The finished product looks like this:
It's not that difficult to follow, just very tedious to write. This function takes any character, and then converts it to the Morse representation of that character, or ignores it if the character is invalid. So, the next step is to take the finished product, created in the parseLine function, and then output the Morse code representation.
The parseLine function was really one of the more complicated parts, not because there's any difficult code, but because some of Morse code is based on what the input was, and that determines how to handle it. So, I decided to implement a few extra parts to parseLine, that would be handled by the Morse parser as it outputs. This is parseLine function in it's mostly final form:
As you can see, I've already started implementing some code to have the output parser correctly pause between characters and punctuation. Handling spaces (which separate words) is already managed, so there's no need to write a hack for that. However, between every letter and punctuation I needed to add a space, so it would properly parse and pause as it's supposed to.
So, finally it's time to write the code parser/output and I realised I need some value to equal the basic unit of time. So, one unit of time passing should equal... I decided on half a second. I created that as a constant so it could be changed either for testing or because the value is too short/too long. And now for the hard part... I need to somehow split the input into multiple different sections based on various meaning. For starters, I need to separate each letter, that way there can be a proper pause between letters. Of course, there also needs to be a separation of words, that way the proper delay can exist between words. This sounds like a multi-dimensional array... First split by full stop, then split by words, then split by letters, then split by characters. This means I will need to modify the parseLine function, because currently it's not giving any indication where each split should occur.
Alright, time to get back into it... First thing I decided to do was change how spaces are handled. Initially I had space transfer directly into the code, but now I realise that a space character should be used to designate every separate character. So, now I need to handle spaces properly... Well, space characters separate words, right? So, I used a new self designated key word, WORD. That way I can split strings by the existence of WORD. That leaves blank spaces for characters. I retained the STOP key word, as I felt that there was no need to change that.
Finally, ready to tackle this stupid output function and finally see something! Except... C++ doesn't have string splitting! So, time to write in an algorithm for that. This one I took from online, as I honestly had no idea how to do this (and it's outside of the scope of this project). So, I will borrow something...
Now that that's done, I can finally get to writing the output function! This one took a long time and a lot of loops and vectors. I won't bore you with the details (full code will be at the end). While writing this function, I went through and modified a lot of the existing code to make more sense in what it does.
So, initial compiling showed me that I missed a lot of mistakes, so fixed those. Finally, time to test the first input string... My first test showed me what happens when you forget to delay from off.
Well, when testing I found out that the LED isn't correctly setup (thank you, Joseph) so it cuts in and out sometimes. And I noticed that it always shows dash. So, I had to debug that.
Further testing also shows that it only blinks twice, not three times (I was testing with S and O). So, more testing... Which means more outputs (because I don't know how to use a debugger). And, as I suspected, it always parsed as a dash. And then I realised that I did something wrong. I needed another loop to get through each dot and dash. So, I created the final loop and ran some tests. To my surprise, I did it!
So, as I said, code will be after this paragraph. This project took me quite a while to complete, probably around 3 hours, but was done almost entirely by myself, with as little internet as I could do, and learning some new stuff.
So, the first thing I did? Well, I have an LED setup to be directly connected to the header (because I don't have a bread board) and so obviously the first step is to flash the LED to show that it works. That was simple, I ran a quick Python script to flash it on and off. Then I modified that script to do it forever (which lead to a bug in the code). Turns out, Raspbian comes with a Python module for GPIO control. Well, I like working in C++, so then came the challenge.
How to control the GPIO header with C++? Raspbian doesn't have a built-in library for that, but it does come with a custom library called wiringPi, which integrates similar controls to an Arduino board. So, I got the LED blinking and had to come up with a new trick. Now, when I was going baremetal I wanted to integrate some sort of Morse code system onto the board. That meant I needed a UI, keyboard interrupts, and, obviously, a keyboard. Well, thanks to installing Raspbian I no longer need to worry about the low level crap that would've taken me years to develop because Broadcom won't release their board documentation (long rant on that one...).
Well, with the basics setup I got to work (I actually wrote this post and the code at the same time). First thing I needed to do was string parsing. Well, technically first thing to do was create an infinite loop that takes in user input and has some way to exit. Anyway, once I did that it was a matter of parsing the user input into written Morse code, then parsing the code and manipulating GPIO pins.
int main() { setup(); std::string input; do { std::cout << "Line (q to quit): "; std::getline(std::cin, input); if(input == "q" || input == "quit") break; std::string code = parseLine(input); showCode(code, GPIO18); } while(true); return 0; }
At first, there wasn't much abstraction. I used pin 18 (code 1 with wiringPi) with an LED attached. A little bit into the code I realised that I wanted some way to control any pin, so next I changed some function names and implemented one layer of abstraction, setting a pin number. Next, I realised that Morse code is usually handled through sound, not light, so I wanted to implement a handler for that. Well, that level of abstraction was easy. Just set a pin to high for the designated amount of time then set it low. With a speaker hooked up, it should work (I have no way to test this). That's all the theory I needed, so I got back to coding the first parser.
The finished product looks like this:
std::string getCode(char alphaNumeric) { switch(alphaNumeric) { case 'a': return ".-"; case 'b': return "-..."; case 'c': return "-.-."; case 'd': return "-.."; case 'e': return "."; case 'f': return "..-."; case 'g': return "--."; case 'h': return "...."; case 'i': return ".."; case 'j': return ""; case 'k': return ""; case 'l': return ".-.."; case 'm': return "--"; case 'n': return "-." case 'o': return "---"; case 'p': return ".--."; case 'q': return "--.-"; case 'r': return ".-."; case 's': return "..."; case 't': return "-"; case 'u': return "..-"; case 'v': return "...-"; case 'w': return ".--"; case 'x': return "-..-"; case 'y': return "-.--"; case 'z': return "--.."; case '0': return "-----"; case '1': return ".----"; case '2': return "..---"; case '3': return "...--"; case '4': return "....-"; case '5': return "....."; case '6': return "-...."; case '7': return "--..."; case '8': return "---.."; case '9': return "----."; case '.': return ".-.-.-"; case ',': return "--..--"; case ':': return "---..."; case '?': return "..--.."; case '\'': return ".----."; case '-': return "-....-"; case '/': return "-..-."; case '(': case ')': return "-.--.-"; case '"': return ".-..-."; case '@': return ".--.-."; case '=': return "-...-"; default: return ""; } }
It's not that difficult to follow, just very tedious to write. This function takes any character, and then converts it to the Morse representation of that character, or ignores it if the character is invalid. So, the next step is to take the finished product, created in the parseLine function, and then output the Morse code representation.
The parseLine function was really one of the more complicated parts, not because there's any difficult code, but because some of Morse code is based on what the input was, and that determines how to handle it. So, I decided to implement a few extra parts to parseLine, that would be handled by the Morse parser as it outputs. This is parseLine function in it's mostly final form:
std::string parseLine(std::string line) { std::string code = ""; for(char &c : line) { if(c == ' ') { code += " "; continue; } code += getCode(tolower(c)); // Writing some hacks to make punctuation work correctly if(c == '.') { code += "STOP"; } } return code; }
As you can see, I've already started implementing some code to have the output parser correctly pause between characters and punctuation. Handling spaces (which separate words) is already managed, so there's no need to write a hack for that. However, between every letter and punctuation I needed to add a space, so it would properly parse and pause as it's supposed to.
So, finally it's time to write the code parser/output and I realised I need some value to equal the basic unit of time. So, one unit of time passing should equal... I decided on half a second. I created that as a constant so it could be changed either for testing or because the value is too short/too long. And now for the hard part... I need to somehow split the input into multiple different sections based on various meaning. For starters, I need to separate each letter, that way there can be a proper pause between letters. Of course, there also needs to be a separation of words, that way the proper delay can exist between words. This sounds like a multi-dimensional array... First split by full stop, then split by words, then split by letters, then split by characters. This means I will need to modify the parseLine function, because currently it's not giving any indication where each split should occur.
Alright, time to get back into it... First thing I decided to do was change how spaces are handled. Initially I had space transfer directly into the code, but now I realise that a space character should be used to designate every separate character. So, now I need to handle spaces properly... Well, space characters separate words, right? So, I used a new self designated key word, WORD. That way I can split strings by the existence of WORD. That leaves blank spaces for characters. I retained the STOP key word, as I felt that there was no need to change that.
Finally, ready to tackle this stupid output function and finally see something! Except... C++ doesn't have string splitting! So, time to write in an algorithm for that. This one I took from online, as I honestly had no idea how to do this (and it's outside of the scope of this project). So, I will borrow something...
Now that that's done, I can finally get to writing the output function! This one took a long time and a lot of loops and vectors. I won't bore you with the details (full code will be at the end). While writing this function, I went through and modified a lot of the existing code to make more sense in what it does.
So, initial compiling showed me that I missed a lot of mistakes, so fixed those. Finally, time to test the first input string... My first test showed me what happens when you forget to delay from off.
Well, when testing I found out that the LED isn't correctly setup (thank you, Joseph) so it cuts in and out sometimes. And I noticed that it always shows dash. So, I had to debug that.
Further testing also shows that it only blinks twice, not three times (I was testing with S and O). So, more testing... Which means more outputs (because I don't know how to use a debugger). And, as I suspected, it always parsed as a dash. And then I realised that I did something wrong. I needed another loop to get through each dot and dash. So, I created the final loop and ran some tests. To my surprise, I did it!
So, as I said, code will be after this paragraph. This project took me quite a while to complete, probably around 3 hours, but was done almost entirely by myself, with as little internet as I could do, and learning some new stuff.
#include <wiringPi.h> #include <iostream> #include <string> #include <vector> #define GPIO18 1 void setup(); void pinHigh(int pin); void pinLow(int pin); std::string parseLine(std::string line); std::string getCode(char alphaNumeric); void showCode(std::string code, int pin); std::vector<std::string> split(std::string s, std::string delim); int main() { setup(); std::string input; do { std::cout << "Line (q to quit): "; std::getline(std::cin, input); if(input == "q" || input == "quit") break; std::string code = parseLine(input); showCode(code, GPIO18); } while(true); return 0; } void setup() { wiringPiSetup(); } void pinHigh(int pin) { digitalWrite(pin, HIGH); } void pinLow(int pin) { digitalWrite(pin, LOW); } std::string parseLine(std::string line) { std::string code = ""; for(char &c : line) { if(c == ' ') { code += "WORD"; continue; } code += getCode(tolower(c)); // Writing some hacks to make punctuation work correctly if(c == '.') { code += "STOP"; } code += " "; } return code; } std::string getCode(char alphaNumeric) { switch(alphaNumeric) { case 'a': return ".-"; case 'b': return "-..."; case 'c': return "-.-."; case 'd': return "-.."; case 'e': return "."; case 'f': return "..-."; case 'g': return "--."; case 'h': return "...."; case 'i': return ".."; case 'j': return ""; case 'k': return ""; case 'l': return ".-.."; case 'm': return "--"; case 'n': return "-."; case 'o': return "---"; case 'p': return ".--."; case 'q': return "--.-"; case 'r': return ".-."; case 's': return "..."; case 't': return "-"; case 'u': return "..-"; case 'v': return "...-"; case 'w': return ".--"; case 'x': return "-..-"; case 'y': return "-.--"; case 'z': return "--.."; case '0': return "-----"; case '1': return ".----"; case '2': return "..---"; case '3': return "...--"; case '4': return "....-"; case '5': return "....."; case '6': return "-...."; case '7': return "--..."; case '8': return "---.."; case '9': return "----."; case '.': return ".-.-.-"; case ',': return "--..--"; case ':': return "---..."; case '?': return "..--.."; case '\'': return ".----."; case '-': return "-....-"; case '/': return "-..-."; case '(': case ')': return "-.--.-"; case '"': return ".-..-."; case '@': return ".--.-."; case '=': return "-...-"; default: return ""; } } void showCode(std::string code, int pin) { // Takes parsed code, then parses it into the pin // to output Morse code const int DELAY = 500; // This is where it gets confusing... // Split the code into sentences, by splitting where it says STOP std::vector<std::string> splitByStop = split(code, "STOP"); for(auto line : splitByStop) { // Next, we need the sentence split into words // So split line by WORD std::vector<std::string> words = split(line, "WORD"); // Now loop through each word for(auto word : words) { // Now split each word into characters, which // is easily done through spaces std::vector<std::string> chars = split(word, " "); // And now we go through each character, which is already split // into the working code, so... // Also, have to manually convert from string to char for(auto morseCode : chars) { // Finally made it to the end, // where we can output the code! for(char dotOrDash : morseCode) { pinHigh(pin); if(dotOrDash == '.') delay(DELAY); else delay(DELAY*3); pinLow(pin); delay(DELAY); } } // Finally, delay for the proper amount of time // which is 3 units of time (one unit is already delayed // by the previous loop) delay(DELAY*2); } // Finally, delay for the proper amount of time. // In this case, because the word has already delayed, // I will be using a smaller value here, but it will // still come out to the be right time delay (7 units of time) delay(DELAY*4); } } std::vector<std::string> split(std::string s, std::string delim) { std::vector<std::string> tokens; std::string token; size_t pos = 0; while((pos = s.find(delim)) != std::string::npos) { token = s.substr(0, pos); tokens.push_back(token); s.erase(0, pos + delim.length()); } tokens.push_back(s); return tokens; }
Comments
Post a Comment