Front view of the Morse Stars device. Some of the star lights are on at various brightness and all of the button indicator lights are off.
This project is a wall decoration/memory device that displays various independently blinking LEDs, each of which displays a unique Morse Code pattern inputted by the user. The design is intended to be somewhat reminiscent of a night sky, with each LED representing a star. On the bottom right of the device there are 3 buttons and 1 switch, each with an indicator light to indicate when it is pressed. On the side of the device there is a power switch which disconnects the Arduino from all the components, turning them all off while keeping the LED data safely stored.
An overall view of the device. All of the star and indicators are off, and all inputs are in their off positions.
Another overall view of the device as it rests on one of the Physical Computing Lab tables for scale. All of the star and indicators are off, and all inputs are in their off positions.
A close up front view of the control panel section of the device. The words "DOT", "DASH", and "SPACE" are laser cut into the material and each has a button and a white LED next to it. I chose these buttons because their shape is reminiscent of a classic telegraph key, which was used to transmit Morse Code. Next to the buttons is the word "RECORDING" with a switch and a red LED next to it.
A close up view of the control panel portion of the device. All components are off, except for the power switch which is in the on position.
The same close up view of the control panel portion of the device. The recording swtich is in the on position, causing the red LED to be lit. Simultaneously, a user is pressing the dot button, causing the corresponding white LED to be lit.
In this video I show how a user inputs a Morse Code pattern. First, I put the recording switch into the on position, lighting up the red LED. Then, I enter an example pattern (short short short) by pressing the corresponding button(s). Each time I press a button, the corresponding LED lights up. Lastly, I return recording switch to the off position, turning off the red LED and assigning the inputted pattern to an LED/star. The star then blinks this pattern with a break in between repititons, slowly fading over time.
This video shows me inputting several unique patterns into the device. Each time a new pattern is entered, it is assigned to a random empty LED and begins blinking its unique pattern at a random pace and independent of other LEDs. All the LEDs are slowly fading in brightness with each blink. However, when a pattern is entered that already exists, the existing LED's brightness is refreshed back to the maximum.
A close view of the initial wiring of the circuit on a breadboard. This part was relatively easy, as the device is fairly simple in terms of hardware.
A close up picture of me testing out the input buttons. As I press the button, the corresponding white LED lights up.
This video shows when I got the fade in and out and pattern functions for the LEDs to work independently for a subset of 3 of the LEDs. The video shows me inputting a test pattern into a test LED as another LED blinks a different pattern independently. The two LEDs blink at different randomly determined rates as intended. This represented a major milestone in my software process, however at this point the pattern assigning logic was still not working correctly and the system still broke when applied to the full collection of LEDs.
This video shows when I got all of the software systems to work. The video shows a test where I got all of the LEDs to flash a unique pattern independently of each other at different rates. The software still needed some tweaking of values after this point but the core functionality was working.
After finishing the software, I moved onto soldering the hardware. This picture is a close up view after the soldering of the buttons and LEDs.
After soldering all the electronics, I began to fit them into the laser cut housing I designed for them. This picture is a close up view of the backside of the control panel. I am testing all of the connections between the switches and the LEDs.
This picture shows all of the electrical connections functioning properly after being secured within the device.
This is a close up view of all of the electronics within their housing.
My software was by far the hardest portion of my project. I didn't realize that it would be so complicated going into it, but in order to efficiently implement the features I wanted (independent patterns, dimming, etc.) I would need to create an object for each LED that would contain its pattern, organize its timers, dimming rate, and lifespan, among other variables. Even though I know how to do this in theory, I was not familiar with C++ syntax for methods and classes. In the process of learning this syntax, I realized that it was significantly different from what I was familiar with. On top of syntax, another significant software challenge I faced was the constraint of the Arduino's memory and processing power. I had always assumed that the limits for these parameters were large enough that I would not reasonably need to consider reaching them, which resulted in several hours of frustration as I repeatedly misdiagnosed overflow errors as other problems. It turned out that the Arduino was unable to handle 30 LED objects as I had defined them, and I ended up needing to settle for only 10 functioning LEDS in the final design.
At our in class crit, the concept and execution were overall received very well. In regard to the overall idea, one person said "It’s about memory, fade memory, and expression – a beautiful sentimental idea" which meant a lot to me. These were some of my main design objectives; I really wanted the function of my device to carry significance to the user and facilitate self reflection, mindfulness, and appreciation---which people seemed to pick up on. For the execution, one reviewer said "It’s great that it was tested with other people. The three separate buttons [are] very accessible for people that know nothing about Morse Code." This was another point that I was looking to emphasize in my design. I wanted Morse Stars to be easy and intuitive to use, even though it involves an outdated encoding system that the majority of the population may only be vaguely familiar with. This is why I elected to have three distinct buttons for dot, dash, and break, as opposed to the one button used by professional operators. Overall, I am very happy with how the project turned out for the scope. It was most definitely not everything I imagined it being, but I hit all of the major points that I was looking to achieve. All of the functionality works as intended and it is aesthetically pleasing, so I am very satisfied. Even though I missed several features, this also taught me a valuable lesson in scoping and planning projects in the future. My original intent was to create a wooden backing with holes in it for the LEDs and painting it with a night sky pattern, not considering the fact that I did not have access to any wood facilities on campus and had no real experience with paints. While I'm sure I would be able to do something like this in a future project, I can't just assume that I will be able to do everything regardless of time frame. I might make another iteration of this project over a much longer time frame which would include these features, along with a Morse Code reference sheet/panel next to the control panel to enable use of the device for all without an external reference. Additionally, I am considering implementing different pitch sounds for each button press to provide more feedback to the user.
/*
Morse Stars
This project is a wall decoration/memory device that displays various independently blinking LEDs,
each of which displays a unique Morse Code pattern inputted by the user. Each LED blinks its given
pattern by fading in and out of it's max RGB value. Each LED has a random fading rate. Over time,
each LED will fade out until it turns off entirely. All LED data is contained within LED objects.
There is a switch to enable recording of a pattern. When the software is recording, the short, long,
and break buttons are pressed by the user to express their desired pattern. Upon the end of recording,
if the pattern is unique it is assigned to a random empty LED. If no empty LEDs exist, it will replace
the oldest LED. If the pattern is not unique, it will refresh the brightness of the existing LED
with the corresponding pattern.
Pin mapping:
Arduino pin | role | details
------------------------------
3 input button
5 input button
7 input button
8 input switch
10 output RGB LED light strip (10 LEDS)
Released to the public domain by the author, October 2024
Andreas Wieslander, awieslan@andrew.cmu.edu
*/
#include <PololuLedStrip.h> //the library required for the LED strip
class Led { //each LED on the LED strip is represented by a corresponding LED object, which contails all the relevant data
public: //delcaring all the variables, methods, and constructors for each LED object
Led(int ind, String pat); //constructors
Led(int ind);
Led();
int index; //index in the LED array
String pattern;
unsigned int patternIndex; //used for keeping track of which letter in the sequence is currently being displayed
double brightness; //max brightness as a percent
void setBrightness(double pct);
int r;
int g;
int b;
rgb_color getRGB(); //returns the color of the LED in the format used by the strip
double MINRATE;
double MAXRATE;
double fadeRate; //the rate at which the LED fades in and out
void increaseBrightness();
void decreaseBrightness();
String getPatternState();
int fadeState; //represents which fading process the LED is currently preforming
int getFadeState();
int counter;
int targetTime;
int NORMALTARGET;
void nextLetter();
unsigned int lifespan; //how long until the LED turns off in ms
unsigned int MAXLIFESPAN = 60000;
};
Led::Led(int ind, String pat) { //creating an LED with an index on the strip and a pattern
index = ind;
pattern = pat + " ";
patternIndex = 0;
if (!(this->getPatternState().equals(" "))) { //determines if the LED should start on or off
fadeState = 1;
} else {
fadeState = 0;
}
brightness = 0;
r = 0;
g = 0;
b = 0;
MINRATE = 1;
MAXRATE = 100;
randomSeed(analogRead(random(0, 6)));
fadeRate = double(random(MINRATE, MAXRATE)) / 10000.0; //picks a random rate
counter = 0;
NORMALTARGET = 1 / fadeRate;
targetTime = NORMALTARGET;
lifespan = MAXLIFESPAN;
}
Led::Led() { //see the previous LED constructor; performs the same functions with an empty pattern
index = -1;
pattern = " ";
if (!this->getPatternState().equals(" ")) {
fadeState = 1;
} else fadeState = 0;
patternIndex = 0;
brightness = 0;
r = 0;
g = 0;
b = 0;
MINRATE = 1;
MAXRATE = 100;
randomSeed(analogRead(0)); //randomizes seed with a random input
fadeRate = double(random(MINRATE, MAXRATE)) / 10000.0;
lifespan = 60000;
}
void Led::setBrightness(double pct) {
if (pct < 0) pct = 0;
if (pct > 1) pct = 1;
int MAXR = 250;
int MAXG = 125;
int MAXB = 0;
r = (MAXR * pct);
g = (MAXG * pct);
b = (MAXB * pct);
this->brightness = pct;
}
rgb_color Led::getRGB() { //returns the color of the LED in a format used by the strip
double pct = (map(this->lifespan, 0, 60000, 0, 100)) / 100.0;
return rgb_color(pct * (this->r), pct * (this->g), pct * (this->b));
}
void Led::increaseBrightness() {
setBrightness(brightness + fadeRate);
}
void Led::decreaseBrightness() {
setBrightness(brightness - fadeRate);
}
String Led::getPatternState() {
return this->pattern.substring(this->patternIndex, this->patternIndex + 1);
}
int Led::getFadeState() {
return this->fadeState;
}
void Led::nextLetter() {
this->patternIndex++;
if (this->patternIndex >= this->pattern.length()) this->patternIndex = 0;
}
//-------------------------------------------END OF LED CLASS------------------------------------------------------
const int SHORTPIN = 7; //pin for short press button
const int LONGPIN = 3; //pin for long press button
const int BREAKPIN = 5; //pin for break button
const int RECORDINGPIN = 8; //pin for recording switch
const int LEDSTRIPPIN = 10; // pin to connect strip to
const int NUMLEDS = 10; // number of LEDs in the strip
PololuLedStrip<LEDSTRIPPIN> ledStrip;
rgb_color TURNEDOFF = rgb_color(0, 0, 0);
rgb_color TURNEDON = rgb_color(250, 100, 0);
rgb_color ledColors[NUMLEDS]; //the array used by the strip
int shortPressed = 0; //keeps track of how long each input is pressed
int longPressed = 0;
int breakPressed = 0;
int recordingPressed = 0;
bool shortDown = false;
bool longDown = false;
bool breakDown = false;
bool recording = false;
String tempPattern = "";
Led leds[NUMLEDS]; //this is the array that stores all the LED objects
void setup() {
pinMode(SHORTPIN, INPUT); //initializes pins
pinMode(LONGPIN, INPUT);
pinMode(BREAKPIN, INPUT);
pinMode(RECORDINGPIN, INPUT);
leds[0] = Led(0, "sss lll sss"); //led 0 is a test LED
ledColors[0] = rgb_color(0, 0, 0);
for (int i = 1; i < NUMLEDS; i++) { //initializes each LED to be empty
String pat = "";
ledColors[i] = rgb_color(0, 0, 0);
leds[i] = Led(i, pat);
}
}
void loop() {
shortDown = digitalRead(SHORTPIN); //checks if each input is on or off
longDown = digitalRead(LONGPIN);
breakDown = digitalRead(BREAKPIN);
recording = digitalRead(RECORDINGPIN);
if (shortDown) { //if each of the buttons are pressed it incriments the respective timer
shortPressed++;
} else shortPressed = 0;
if (longDown) {
longPressed++;
} else longPressed = 0;
if (breakDown) {
breakPressed++;
} else breakPressed = 0;
if (recording) { //if currently recording
recordingPressed++;
if (shortPressed == 1) tempPattern += "s"; //if the button was just pressed, add it to the pattern
if (longPressed == 1) tempPattern += "l";
if (breakPressed == 1) tempPattern += " ";
} else if (recordingPressed > 0) { //if just stopped recording
bool found = false;
bool exists = false;
int index = -1;
for (int i = 0; i < NUMLEDS; i++) { //check each pin
if (leds[i].pattern.equals(tempPattern + " ")) { //if the pattern already exists
exists = true; //the pattern exists
index = i;
}
if (leds[i].pattern.equals(" ")) { //if empty
found = true; //there is one or more empty pin(s)
}
}
if (exists) { //if pattern already exists
leds[index].lifespan = leds[index].MAXLIFESPAN; //refresh existing led
} else if (found) { // if there is one or more empty pin(s)
bool resolved = false;
while (!resolved) { //until one is found
randomSeed(analogRead(0));
int i = random(0, NUMLEDS); //pick a random index
if (leds[i].pattern.equals(" ")) { //if empty
index = i;
resolved = true; //found
}
}
leds[index] = Led(index, tempPattern); //create the LED
} else { //if no empty pins
unsigned int min = INT8_MAX;
for (int i = 0; i < NUMLEDS; i++) { //check each led
if (leds[i].lifespan < min) { //find dimmest pin
min = leds[i].lifespan;
index = i;
}
}
leds[index] = Led(index, tempPattern); //create the LED
}
tempPattern = "";
recordingPressed = 0; //reset variables
}
for (int i = 0; i < NUMLEDS; i++) { //go through this entire process for each LED, every 1ms
if (leds[i].getFadeState() == 1) { //if fading in
if (leds[i].brightness < 1) { //if below max
leds[i].increaseBrightness();
} else { //if rached max
if (leds[i].getPatternState().equals("l")) { //if long
leds[i].targetTime = leds[i].NORMALTARGET; //start waiting
leds[i].counter = 0;
leds[i].fadeState = 0;
} else { //if short
leds[i].fadeState = -1;
}
}
} else if (leds[i].getFadeState() == -1) { //if fading out
if (leds[i].brightness > 0) { //if above min
leds[i].decreaseBrightness();
} else { //if reached min
leds[i].counter = 0;
leds[i].nextLetter();
if (leds[i].getPatternState().equals(" ")) { //if next letter is a break
leds[i].fadeState = 0;
leds[i].targetTime = leds[i].NORMALTARGET * 3; //start waiting
} else leds[i].fadeState = 1;
}
} else { //if waiting
if (leds[i].counter < leds[i].targetTime) { //if time not reached
leds[i].counter++;
} else { //if time rached
leds[i].counter = 0;
if (leds[i].getPatternState().equals(" ")) { //if this was a break
leds[i].nextLetter();
if (!leds[i].getPatternState().equals(" ")) { //if next letter is a short or long
leds[i].fadeState = 1;
}
} else { //if this was a long
leds[i].fadeState = -1;
}
}
}
if (leds[i].lifespan != 0) {
leds[i].lifespan--;
}
ledColors[i] = leds[i].getRGB(); //convert and store RGB values in a format readable by the strip
}
leds[0].lifespan = leds[0].MAXLIFESPAN; //refresh test LED
ledStrip.write(ledColors, NUMLEDS); //write all 10 LED's RGB values
delay(1);
}