Gratitude Device
by the Lotuses
Daniel, Rongrong, Siyu
by the Lotuses
Daniel, Rongrong, Siyu
Introduction
For our Physical Computing final project, we teamed up with an assisted living home to create a device that would benefit one of its members. Due to the residents’ specialized needs and their receptiveness to collaborating with us as developing prototypers, the collaboration was a great opportunity to practice designing for real clients with specific needs and constraints. Ultimately, there were more teams than there were residents in the home; thus, we were paid with Lindsey, a resident caretaker. We interviewed Lindsey (see previous post here) to brainstorm potential needs and solutions within her life, and eventually settled on a gratitude-facilitating device that would better allow those under her care to express their thanks to her.
What We Built
Front view of gratitude device plugged in and turned off
Our device allows people who struggle to compose written sentences to express their feelings of appreciation towards others– namely, their caretaker(s). It accomplishes this by displaying a series of pre-formed messages and images on a screen. The user can toggle between message options using a knob, and can then select and append them to a running note shown on the right half of the screen. When the user is satisfied with his/her composition, he/she can press the ‘print’ button, which releases a sheet of paper containing their personalized note. The user can then hand out or leave their gratitude note to whomever they wish, spreading some positivity within the home.
Isometric view of device plugged in and turned off
Front view of device turned on and initialized
Side view of device plugged in and turned off
Isometric view of deviceplugged in and turned off from opposite angle
In the video, the user is shown to turn on the device, which initializes and displays message options on the left of the screen. The user then uses the select button to append "Thank you!" to the right of the screen, and prints the message out of the slot at the top. The note, which contains lines for the note-maker to sign his/her name, is then torn against the slightly serrated edge in the machine.
Shown above is the initial proposal for the gratitude device, including brainstormed ideas and relevant considerations. Initially, the device was shaped and oriented quite differently from our final design; additionally, it contained more buttons than our actual prototype.
Narrative Sketch:
Mary has just finished her daily activities and is getting ready for bed. In a moment of reflection, she remembers that her caretaker, Jenny, was particularly kind and helpful to her during dinner that night. Mary wishes to do something kind for Jenny, so she walks over to the resident gratitude device, which is sitting on a nearby table. Mary struggles to form sentences on her own; however, using the device, she can easily compose a wholesome note using nothing more than a knob and some buttons. She picks a series of sayings and images that express her energy, prints and signs the letter, and leaves it on Jenny’s desk. Jenny, after clocking out from a long day of work, sees the letter on her desk, reads it, adds it to her collection of notes from her residents, and smiles.
Prototype
This prototype was designed to help answer the design question: how can we create a device that can be used by everyone in the assisted living community, given their vast range of abilities? Our final prototype, which aimed to answer this question, ended up being a small device that sits upon a tabletop. To quickly reiterate, it displays a series of message options on a screen, which the user can use to compose a note expressing their thanks to someone that they care about. The note can also be printed onto a sheet of receipt paper and gifted.
Two physical prototypes with a series sticky notes representing the screen
Testing out different formats to print the messages
Physical prototypes of buttons and dial location for user interface
Above, the prototype is shown in use. The knob is turned which toggles through different messages on the left side of the screen. The select button is then pressed, which adds the message. Finally, the print button is selected, which prints out a receipt from the top of the device.
Testing out receipt printer individually
Zoom meeting with Lindsey to demonstrate prototype and ask questions
Different iterations of interface designs
Going into the design process, our biggest challenge was designing for a range of people possessing different mental and physical capabilities. We were told that the residents were able to read basic sentences; thus, we operated under that assumption during early ideation. We considered many options regarding message composition, and our biggest struggle was to create an intuitive user interface. On one hand, we wanted to make our UI/UX as straightforward as possible (possibly by implementing large buttons); however, on the other hand, we hoped to avoid the “remote control effect” of creating a maze of buttons that are cluttered and overwhelming. Eventually, we decided on a scrolling knob interface because it allowed us to minimize the number of buttons. Even so, we had many further questions to consider; for example, how many message options would be optimal to create a sense of personalizability? What types of messages would accurately capture residents’ sentiments and word choices? Would residents be able to physically turn a knob to the degree of specificity that our plan required? And most importantly, would anyone even want to use a device such as ours?
We had many uncertainties going into prototyping; thus, the in-person meeting with the residents on campus was invaluable. Unfortunately, Lindsey was unable to attend; however, we spoke to Autumn (Lindsey’s coworker/boss) extensively. Autumn surprised us by telling us that most of the residents could barely read. She also took a glance at our proposed messages and informed us that our options were too cheesy and sentimental; additionally, she indicated that our knob was far too small. These insights pushed us to rethink our approach– we realized that our word choices needed to be simplified and "uncheese-ified," and that images/emojis along with larger physical buttons/knobs were important for accessibility. We also took the chance to survey some of the residents themselves– we learned that they were generally receptive to the idea.
After our chat with Autumn, we called Lindsey to touch base. Lindsey, along with confirming some of Autumn’s suggestions, stated that residents often make drawings for her in their free time– this reinforced our hope that they would enjoy gifting notes to their caretakers.
Screen connected to buttons and potentiometer along with test prints
First iteration of UI
Final UI interface
Screen and buttons being mounted onto the front face
Using failed 3D print to layout the internal components of device
Front face secured onto the main body
The virtual prototyping process presented many challenges. Creating the scrolling interface on the LCD screen took many days of trial and error; additionally, once emojis were added to the equation, the entire interface essentially imploded. It took many rounds of debugging and changes in approach to achieve our desired interface. Luckily, existing advice from online users of our particular screen and Claude (an AI) were highly helpful when writing the code.
On the physical side of things, we ran into tolerancing issues, failed prints, and phantom errors upon integration, among other things. Time and time again, we would forget to account for a small corner or outcrop, rendering our PLA shell slightly too small or misaligned. We had to refabricate several pieces many times before everything fit within our desired tolerances. Additionally, when making our custom ‘print’ and ‘select’ buttons, we hoped to superglue PLA shells onto the small buttons that we ordered; however, the glue seeped into the buttons and prevented them from recessing correctly. We ran into several events such as this that required us to take a step back for every two steps forward.
As expected, integration was probably the hardest part of our physical prototyping. The circuit would often stop functioning after being shoved into the PLA shell, prompting us to disassemble, troubleshoot, and start again. From that process, we learned the lesson of being highly meticulous and leaving enough room for components when designing.
In terms of our Gantt chart, we didn’t really stay on schedule. We had a series of setbacks, largely on the physical side of things. Things on the software side stayed relatively on track; however, regarding construction and integration, we had to continuously revise our plan. Luckily, we had built in a lot of buffer time during the final week of construction to account for unforeseen errors, which proved to be a good choice. Overall, the completion of this project was humbling but productive.
During the final critique, we received several suggestions that could help improve our project. A key area of feedback was the overall scale of our device. Many commenters suggested that the screen and buttons should be made larger. When we initially designed the device, we believed that a compact form factor would be the most effective solution; however, we came to realize that while the size may have been suitable for our desired look, it was likely too small for residents with disabilities or members of the older generation. We also received suggestions to increase the size of the dial specifically. Although we had enlarged the potentiometer cap, its size and placement still posed challenges for some residents. Another valuable piece of feedback was the idea of incorporating a way to upload new sentences to the device. This feature would allow the device to be used long-term, rather than repeating the same limited set of sentences.
One major takeaway from this project was the importance of regular communication with the client. While our team made certain assumptions about the device’s functionality, speaking with Lindsey consistently provided helpful and insightful guidance on our prototype questions. Another takeaway was that the assembly process was very difficult to execute, especially with all of the fragile electronic components.
Overall, we believe our group did an excellent job ideating and creating a device that enables the residents to express appreciation to Lindsey. There is certainly room for improvement in terms of ergonomics, scalability, and the replaceability of the receipt printer; however, considering the project’s time constraints, we were proud to deliver a functioning prototype that aligned with our original vision.
One of the biggest challenges we faced was the gap between the client and the end users. While Lindsey was our main point of contact, the residents were the actual users of the device. Unfortunately, we were not able to conduct user testing with the residents during the earlier prototype phases, which made it difficult to anticipate some of the issues that became apparent during the final critique.
Block Diagram
Electrical Schematic
Code
#include <UTFT.h>
#include "Adafruit_Thermal.h"
#include "SoftwareSerial.h"
/**
* Message Creator with T-Chart Interface (largely created with Claude)
*
* This program creates an interface for selecting and displaying
* emoji messages, which can then be printed on a thermal printer.
*/
// Pin mapping:
// Arduino pin | role | description
// ___________________________________________________________________
// A0 | input | Potentiometer for scrolling options
// 2 | input | RX_PIN (Arduino receive -> TX on printer)
// 3 | output | TX_PIN (Arduino transmit -> RX on printer)
// 5 | input | Button for selecting options
// 8 | input | Button for printing message
// 38-41 | output | LCD control pins for ILI9486 display
// Thermal printer pins and setup
#define TX_PIN 3 // Arduino transmit -> RX on printer
#define RX_PIN 2 // Arduino receive -> TX on printer
SoftwareSerial mySerial(RX_PIN, TX_PIN);
Adafruit_Thermal printer(&mySerial);
// Font
extern uint8_t BigFont[];
// Define emoji bitmaps (16x16 pixels each)
unsigned short heart[256] = {
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x4020, 0x8145, 0x8186, 0x3082, 0xFFFF, 0xFFFF, 0x1820, 0x7000, 0x8800, 0x5800, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x60A2, 0xF3EF, 0xF514, 0xFB4D, 0xE8E3, 0x4000, 0x1020, 0xC800, 0xF800, 0xF000, 0xF800, 0x9800, 0xFFFF, 0xFFFF, 0xFFFF, 0x2800, 0xE36D, 0xED75, 0xE228, 0xE000, 0xF800, 0xB800, 0x9000, 0xF800, 0xE000, 0xE000, 0xE000, 0xF800, 0x5000, 0xFFFF, 0xFFFF, 0x58A2, 0xF4D2, 0xE40F, 0xE800, 0xE800, 0xE000, 0xE800, 0xE800, 0xE000, 0xE800, 0xE800, 0xE000, 0xF800, 0x8800, 0xFFFF, 0xFFFF, 0x58E3, 0xF4F3, 0xE30C, 0xE800, 0xE800, 0xE800, 0xE800, 0xE800, 0xE800, 0xE800, 0xE800, 0xE000, 0xF800, 0x9000, 0xFFFF, 0xFFFF, 0x4041, 0xEC92, 0xE34D, 0xE800, 0xE800, 0xE800, 0xE800, 0xE800, 0xE800, 0xE800, 0xE800, 0xE000, 0xF800, 0x7000, 0xFFFF, 0xFFFF, 0xFFFF, 0xBAEB, 0xF4B2, 0xE000, 0xE800, 0xE800, 0xE800, 0xE800, 0xE800, 0xE800, 0xE800, 0xE800, 0xE000, 0x2820, 0xFFFF, 0xFFFF, 0xFFFF, 0x4082, 0xEC92, 0xE9C7, 0xE800, 0xE800, 0xE800, 0xE800, 0xE800, 0xE800, 0xE000, 0xF800, 0x6000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x6104, 0xF3CF, 0xE841, 0xE800, 0xE800, 0xE800, 0xE800, 0xE000, 0xF800, 0x8000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x6145, 0xF208, 0xE800, 0xE800, 0xE800, 0xE000, 0xF800, 0x8800, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x60E3, 0xF0A2, 0xE800, 0xE000, 0xF800, 0x8800, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x6882, 0xF020, 0xF800, 0x9800, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFF 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x9041, 0xC800, 0x0820, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x1800, 0x2000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF
};
unsigned short smile[256] = {
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x18A1, 0x1080, 0x1080, 0x1080, 0x18A1, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x1081, 0x2920, 0xDE20, 0xEE81, 0xE661, 0xEE81, 0xDE21, 0x2100, 0x1081, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x20E0, 0xDD60, 0xEE80, 0xEEA1, 0xFF49, 0xFF6A, 0xFF6A, 0xF6E8, 0xEEA1, 0xDE20, 0x2120, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x1080, 0xD560, 0xFF20, 0xEE80, 0x2941, 0xEEAA, 0xFFD2, 0xEEF1, 0x3163, 0xEEC8, 0xFF41, 0xDE00, 0x1080, 0xFFFF, 0xFFFF, 0xA514, 0x3162, 0xEE60, 0xFF21, 0xEEA8, 0x3183, 0xEEF1, 0xFFD4, 0xEF16, 0x31A5, 0xEEF1, 0xFF6A, 0xEEA1, 0x2921, 0xFFFF, 0xFFFF, 0x20E2, 0xDD61, 0xFEE0, 0xFF01, 0xFF49, 0xF70A, 0xFF72, 0xFF93, 0xFF93, 0xF777, 0xFFB9, 0xFFB2, 0xFF49, 0xDE01, 0x10A0, 0xAD76, 0x18C1, 0xE5A0, 0xFF00, 0xEE81, 0xFF49, 0xFF6A, 0xFF4B, 0xFF91, 0xFF93, 0xFFB4, 0xFFB8, 0xEF32, 0xFF6A, 0xEE61, 0x1080, 0xAD96, 0x20E2, 0xE580, 0xEE60, 0x3161, 0xF6E9, 0xFF6A, 0xFF4A, 0xFF4B, 0xFF71, 0xFFB2, 0xEF11, 0x3163, 0xF6E9, 0xEE81, 0x1080, 0xAD76, 0x18A1, 0xDC80, 0xE5A0, 0x3140, 0xD601, 0xFF69, 0xFF6A, 0xFF6A, 0xFF6B, 0xFF8B, 0xD629, 0x3162, 0xF6E8, 0xEE81, 0x1060 0xB596, 0x2923, 0xC400, 0xFE00, 0xDE00, 0x41C0, 0xD5E1, 0xEEC9, 0xE6A9, 0xEEC9, 0xDE28, 0x41E2, 0xDE68, 0xFF01, 0xD540, 0x18A1, 0xC618, 0xA535, 0x28E1, 0xD460, 0xFE20, 0xDDE0, 0x3180, 0x2121, 0x2921, 0x2121, 0x3181, 0xDE20, 0xFF01, 0xEDA0, 0x20E0, 0xFFFF, 0xFFFF, 0xB5B6, 0x2923, 0xBBC0, 0xECE0, 0xFE00, 0xEE60, 0xE640, 0xEE40, 0xE640, 0xEE60, 0xFF00, 0xFE00, 0xC420, 0x1061, 0xFFFF 0xFFFF, 0xC618, 0xAD75, 0x3163, 0xBBC0, 0xD460, 0xFE00, 0xFE20, 0xFE20, 0xFE20, 0xFE00, 0xE580, 0xC420, 0x28E1, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC638, 0xB596, 0x2103, 0x20E1, 0xC400, 0xD440, 0xCC20, 0xD440, 0xC420, 0x2902, 0x1081, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0xB5B6, 0xA535, 0x2944, 0x20E2, 0x20E2, 0x20C2, 0x2102, 0xA535, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0xB5B6, 0xAD96, 0xB5B6, 0xAD96, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF
};
unsigned short flower[256] = {
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x59C6, 0xAB2D, 0xA34E, 0x51E7, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x7A28, 0xFCD2, 0xFD97, 0xFE19, 0xED55, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x4965, 0xD3AD, 0xFDF8, 0xF576, 0xFD76, 0xFD98, 0x9ACB, 0x59E7, 0xAACA, 0x61E7, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x7A07, 0xEC30, 0xF4D4, 0xA2CA, 0xFCB1, 0xFDB7, 0xFDB7, 0xE4B2, 0x8AAB, 0xFCD4, 0xFD35, 0xF4F4, 0x49C7, 0xFFFF, 0xFFFF, 0xFFFF, 0xEC0F, 0xFDF8, 0xFD76, 0xECB4, 0xB32C, 0xC36D, 0xECEF, 0xD4C3, 0xE44E, 0xFD16, 0xFD76, 0xFE59, 0xBBD0, 0xFFFF, 0xFFFF, 0x8A28, 0xFCB2, 0xFDD8, 0xFD76, 0xFD97, 0xFCF4, 0xA32C, 0xABA4, 0xE40C, 0xFC50, 0xFD76, 0xFD96, 0xFD76, 0xE410, 0xFFFF, 0xFFFF, 0xFFFF, 0xEB4C, 0xFCB3, 0xECD3, 0xBBEF, 0xB32C, 0xA36C, 0x930A, 0xCBAE, 0xBBAE, 0xEC91, 0xFD55, 0xFCD3, 0xA268, 0xFFFF, 0xFFFF, 0xFFFF, 0x2AC7, 0x6268, 0xB34D, 0xD493, 0xF515, 0xF4F4, 0xAAEA, 0xFC91, 0xE4F5, 0x828A, 0x8AC9, 0x6AA8, 0x20C3, 0xFFFF, 0xFFFF, 0x1368, 0x056B, 0x8AAA, 0xFCF5, 0xFD76, 0xFDF8, 0xE491, 0x8A27, 0xFCF4, 0xFDB6, 0xF494, 0x1286, 0x05CB, 0x0BC8, 0xFFFF, 0x1307, 0x05AB, 0x0B27, 0xEBCF, 0xFD76, 0xFDF8, 0xFE18, 0xBB0B, 0xBACA, 0xFD34, 0xF5D7, 0xFDF7, 0x7269, 0x0B87, 0x0CAA, 0xFFFF, 0xFFFF, 0x1A26, 0x08A3, 0x7A07, 0xFC91, 0xFCF3, 0xDB8D, 0x2AA6, 0x6A47, 0xFCB2, 0xFDF7, 0xDC30, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x7207, 0x79C6, 0x1A05, 0x0469, 0x04CA, 0x5B49, 0xAACA, 0x4985, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0B88, 0x068D, 0x02C6, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x1A86, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF
};
unsigned short laugh[256] = {
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xDEE1, 0xE741, 0xEF61, 0xEF61, 0xEF61, 0xD6C1, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xE761, 0xE761, 0xDEE1, 0xDEE1, 0xDEE1, 0xD6E1, 0xE741, 0xEF61, 0xC641, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF7A1, 0xCE61, 0x5AE1, 0xDF01, 0xE741, 0xDF21, 0xE741, 0x6321, 0xBE01, 0xF7A1, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xD6A1, 0xCE61, 0x4A60, 0x8400, 0xA520, 0xB5C1, 0xBE01, 0xA521, 0x8C40, 0x4A40, 0xC621, 0xD6C1, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF781, 0x7BE1, 0xAD60, 0x5B88, 0x52C1, 0x9D01, 0xAD61, 0x5AC1, 0x5329, 0xB5A0, 0x73A0, 0xF7A1, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xE721, 0xEF60, 0xB66A, 0x4B2C, 0x6B60, 0x6340, 0x6320, 0x6B60, 0x4B0B, 0xAE2B, 0xEF60, 0xE741, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xEF60, 0xB64A, 0xAE3C, 0x63D2, 0x8433, 0x9494, 0x9494, 0x8C53, 0x5BB1, 0xA61C, 0xAE4C, 0xEF80, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xBEA9, 0xAE5C, 0xF79D, 0x8DDB, 0x4248, 0x39E7, 0x39E7, 0x4227, 0x857A, 0xF79D, 0xB67D, 0xBEAB, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x64D6, 0xB6DF, 0xD6FE, 0x9E15, 0x9CE0, 0x0000, 0x0000, 0x8C80, 0x9E13, 0xCEDE, 0xC6DF, 0x6D18, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x9615, 0x9E32, 0xCEC3, 0xEF81, 0x9CE1, 0x8C81, 0xEFA1, 0xD6C2, 0xA631, 0x9615, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xE740, 0xEF81, 0xD6E1, 0xE721, 0xE721, 0xDEE1, 0xEF61, 0xE740, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xDF01, 0xE721, 0xDF21, 0xE741, 0xCE61, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF
};
unsigned short rainbow[256] = {
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF1A7, 0xF986, 0xFB04, 0xFB81, 0xFB81, 0xFB04, 0xF986, 0xF1C7, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF247, 0xFC44, 0xC601, 0xA7E2, 0x27EB, 0x27EB, 0xA7E2, 0xC601, 0xFC44, 0xF247, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xE947, 0xFA03, 0x9723, 0x07F4, 0x1596, 0x631F, 0x631F, 0x1596, 0x07F4, 0x9723, 0xFA03, 0xF147, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFA45, 0xE5C2, 0x3FCB, 0x1D3F, 0x793F, 0x987E, 0x987E, 0x793F, 0x1D3F, 0x3FCB, 0xE5C2, 0xFA45, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFBA4, 0xB7E0, 0x07F6, 0x4A9F, 0xA0DD, 0xFFFF, 0xFFFF, 0xA0DD, 0x429F, 0x07F5, 0xB7E0, 0xFBA4, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFB43, 0x87E5, 0x0EB9, 0x623F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x623F, 0x0EB8, 0x87E5, 0xFB43, 0xFFFF, 0xFFFF, 0xFFFF, 0xF906, 0xF4C1, 0x1FED, 0x245F, 0x90FF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x88DF, 0x245F, 0x1FED, 0xF4C1, 0xF906, 0xFFFF, 0xFFFF, 0xFA04, 0xFF41, 0x2FED, 0x2CFF, 0x80FE, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x80FE, 0x2CFF, 0x2FED, 0xFF41, 0xFA04, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF
};
// LCD (ILI9486)
UTFT myGLCD(ILI9486, 38, 39, 40, 41);
// Pins
const int potPin = A0; // Potentiometer for scrolling options
const int buttonPin = 5; // Button for selecting options
const int printButtonPin = 8; // Button for printing message
// Options
const int numOptions = 5;
String options[numOptions] = {
"Thank you!",
"Appreciate you!",
"You're the best!",
"Guess who?",
"Have a nice day!"
};
// Emoji indices that correspond to each option
int optionEmojis[numOptions] = {0, 1, 2, 3, 4}; // 0=heart, 1=smile, 2=flower, 3=laugh, 4=rainbow
// Text symbols for emojis when printing (we chose to comment these out)
const char* emojiSymbols[numOptions] = {
"<3", // Heart
":)", // Smile
"*", // Flower
":D", // Laugh
"()" // Rainbow
};
// Message structure to store text and emoji pairs
struct MessageItem {
String text;
int emojiIndex;
bool isEmoji;
};
// Array to store message items
const int MAX_MESSAGES = 50;
MessageItem messages[MAX_MESSAGES];
int messageCount = 0;
// Selection variables
int selectedOption = 0;
int previousSelectedOption = -1;
float potSensitivity = 2.0;
int potValue = -1;
// Button debounce variables
int buttonState = 0;
int lastButtonState = 0;
unsigned long lastDebounceTime = 0;
unsigned long debounceDelay = 50;
// Print button debounce variables
int printButtonState = 0;
int lastPrintButtonState = 0;
unsigned long lastPrintDebounceTime = 0;
// Display layout constants
const int centerY = 180; // Y-position for the scrolling section
const int safeCutoffY = 65; // Prevent drawing above this Y position (just below horizontal line)
/**
* Draws a bitmap with transparent background
* Transparent color is defined by img_background
*/
void drawBitmapWithBackground(int x, int y, int sx, int sy, unsigned short* data, unsigned short img_background) {
for (int ty = 0; ty < sy; ty++) {
for (int tx = 0; tx < sx; tx++) {
unsigned short color = data[ty*sx+tx];
if (color == img_background) continue;
myGLCD.setColor(color);
myGLCD.drawPixel(x+tx, y+ty);
}
}
}
/**
* Draws the main interface with T-chart design
* Creates a clean layout with light gray header sections
*/
void drawInterface() {
// Set white background for entire screen
myGLCD.setColor(255, 255, 255);
myGLCD.fillRect(0, 0, 479, 319);
// Draw black border
myGLCD.setColor(0, 0, 0);
myGLCD.drawRect(0, 0, 479, 319);
// Fill header sections with very light gray (even lighter than before)
myGLCD.setColor(248, 248, 248);
myGLCD.fillRect(1, 1, 238, 59); // Left header section
myGLCD.fillRect(240, 1, 478, 59); // Right header section
int leftCenter = 120;
int rightCenter = 360;
// --- Left Header: "Select" ---
int leftX = leftCenter - (6 * 16 / 2);
myGLCD.setColor(0, 0, 0); // Pure black text
myGLCD.setBackColor(248, 248, 248); // Match the background color of header section
myGLCD.setFont(BigFont);
myGLCD.print("Select", leftX, 20);
// --- Right Header: "Message" ---
int rightX = rightCenter - (7 * 16 / 2);
myGLCD.setColor(0, 0, 0); // Pure black text
myGLCD.setBackColor(248, 248, 248); // Match the background color of header section
myGLCD.setFont(BigFont);
myGLCD.print("Message", rightX, 20);
// Draw horizontal line for T-chart at Y=60
myGLCD.drawLine(0, 60, 479, 60);
// Draw vertical line for complete division
myGLCD.drawLine(239, 0, 239, 319);
drawStaticSelectionBox();
}
/**
* Draws the selection box for displaying the current option
* Uses a subtle light gray design
*/
void drawStaticSelectionBox() {
// Center position for the box
int centerY = 180;
int boxHeight = 45; // Slightly smaller box height for better spacing
// Very light gray background for the selection box
myGLCD.setColor(240, 240, 240);
myGLCD.fillRoundRect(20, centerY - boxHeight, 220, centerY + boxHeight);
// Light gray border for the bounding box
myGLCD.setColor(200, 200, 200);
myGLCD.drawRoundRect(20, centerY - boxHeight, 220, centerY + boxHeight);
}
/**
* Updates the displayed option on the left side of the screen
* Shows the current selection with adjacent options in gray
*/
void updateOptionDisplay() {
int centerX = 120;
int centerY = 180; // Match centerY in drawStaticSelectionBox
int boxHeight = 45; // Match boxHeight in drawStaticSelectionBox
int visibleArea = centerY + boxHeight;
// Clear the surrounding option areas to prevent overlapping text
myGLCD.setColor(255, 255, 255);
// Only clear below the safe cutoff line
myGLCD.fillRect(20, max(safeCutoffY, centerY - boxHeight - 80), 220, centerY - boxHeight - 1);
// Clear area below selection box
myGLCD.fillRect(20, centerY + boxHeight + 1, 220, centerY + boxHeight + 80);
// Only update if the selected option has changed
if (selectedOption != previousSelectedOption) {
// Clear the region where the text is going to change
myGLCD.setColor(255, 255, 255);
myGLCD.fillRect(20, centerY - boxHeight, 220, centerY + boxHeight);
// Redraw the bounding box
drawStaticSelectionBox();
// Update text display with emoji (changed to black text)
myGLCD.setColor(0, 0, 0); // Pure black text for options
myGLCD.setFont(BigFont);
myGLCD.setBackColor(240, 240, 240); // Match background color with the bounding box
drawOptionWithEmoji(options[selectedOption], optionEmojis[selectedOption], centerX, centerY - 20, visibleArea);
// Update previous state
previousSelectedOption = selectedOption;
}
// Draw the surrounding options in black (changed from medium gray)
myGLCD.setBackColor(255, 255, 255);
myGLCD.setColor(0, 0, 0); // Black for surrounding options
// Show black text for available options
int aboveIndex = wrapIndex(selectedOption - 1, numOptions);
if (selectedOption > 0) {
drawOptionWithEmoji(options[aboveIndex], optionEmojis[aboveIndex], centerX, centerY - 100, centerY - boxHeight);
}
int belowIndex = wrapIndex(selectedOption + 1, numOptions);
if (selectedOption < numOptions - 1) {
drawOptionWithEmoji(options[belowIndex], optionEmojis[belowIndex], centerX, centerY + 60, centerY + boxHeight + 80);
}
// Reset colors
myGLCD.setColor(0, 0, 0);
myGLCD.setBackColor(255, 255, 255);
}
/**
* Updates the message display in the right panel
* Shows all collected messages and emojis with word wrapping
*/
void updateMessageDisplay() {
// Clear the message area
myGLCD.setColor(255, 255, 255);
// Only clear below the safe cutoff line
myGLCD.fillRect(240, safeCutoffY, 479, 319);
// Align with the top option on left side
int startX = 270; // Space from middle line
int startY = 85; // Aligned with top option on left
int currentX = startX;
int currentY = startY;
int lineHeight = 24;
int charWidth = 16;
int emojiWidth = 16;
int maxX = 460; // Maximum X position before wrapping
int spaceWidth = 10; // Width of a space character
// Set text color to black and background to white
myGLCD.setColor(0, 0, 0);
myGLCD.setFont(BigFont);
myGLCD.setBackColor(255, 255, 255);
for (int i = 0; i < messageCount; i++) {
if (messages[i].isEmoji) {
// This is an emoji
// Check if we need to wrap to next line
if (currentX + emojiWidth > maxX) {
currentX = startX;
currentY += lineHeight;
// Check if we're out of vertical space
if (currentY > 300) {
break;
}
}
// Only draw if below cutoff
if (currentY >= safeCutoffY) {
// Draw the emoji
drawEmoji(messages[i].emojiIndex, currentX, currentY);
}
// Move cursor position
currentX += emojiWidth + 4; // Add space after emoji
}
else {
// This is text
String text = messages[i].text;
// Skip if text is empty
if (text.length() == 0) {
continue;
}
// Add space before text if not at the start of a line
if (currentX > startX) {
currentX += spaceWidth;
}
// Process text word by word for better wrapping
int textPosition = 0;
String word;
int wordEnd;
while (textPosition < text.length()) {
// Find next word
wordEnd = text.indexOf(' ', textPosition);
if (wordEnd == -1) {
wordEnd = text.length();
}
word = text.substring(textPosition, wordEnd);
int wordWidth = word.length() * charWidth;
// Check if we need to wrap to next line
if (currentX + wordWidth > maxX) {
currentX = startX;
currentY += lineHeight;
// Check if we're out of vertical space
if (currentY > 300) {
i = messageCount; // Exit both loops
break;
}
}
// Only draw if below cutoff
if (currentY >= safeCutoffY) {
// Reset text color to black every time we print
myGLCD.setColor(0, 0, 0);
myGLCD.setBackColor(255, 255, 255);
// Print the word
myGLCD.print(word, currentX, currentY);
}
// Move cursor position
currentX += wordWidth;
// Add space after word if not at the end of the text
if (wordEnd < text.length()) {
currentX += spaceWidth;
}
textPosition = wordEnd + 1;
}
}
}
}
/**
* Draws text with an accompanying emoji
* Handles text wrapping and emoji placement logic
*/
void drawOptionWithEmoji(String text, int emojiIndex, int centerX, int topY, int maxVisibleY) {
int maxCharsPerLine = 13;
int lineHeight = 24;
int charWidth = 16;
String line1 = "", line2 = "";
// Bounding box limits
int boxLeft = 20;
int boxRight = 220;
int boxWidth = boxRight - boxLeft;
int minEmojiSpace = 50; // Minimum space needed for emoji to fit properly
int emojiWidth = 16; // Width of the emoji
int emojiHeight = 16; // Height of the emoji
// Determine if text needs to be wrapped
if (text.length() <= maxCharsPerLine) {
line1 = text;
} else {
int splitAt = text.lastIndexOf(' ', maxCharsPerLine);
if (splitAt == -1) splitAt = maxCharsPerLine;
line1 = text.substring(0, splitAt);
line2 = text.substring(splitAt + 1);
}
// Calculate position for the text
int x1 = max(centerX - (line1.length() * charWidth/2), boxLeft + 5);
// Only print text if it's within the visible area AND below cutoff
if (topY + lineHeight <= maxVisibleY && topY >= safeCutoffY) {
myGLCD.print(line1, x1, topY);
}
// Emoji position variables
int emojiX, emojiY;
bool needsNewLine = false;
// Place emoji after the text, accounting for wrapping
if (line2.length() > 0) {
int x2 = max(centerX - (line2.length() * charWidth/2), boxLeft + 5);
// Only print second line if it's within the visible area AND below cutoff
if (topY + lineHeight <= maxVisibleY && topY + lineHeight >= safeCutoffY) {
myGLCD.print(line2, x2, topY + lineHeight);
}
// Calculate where the line2 text ends
int textEndX = x2 + (line2.length() * charWidth);
// If there's enough space after line2, place emoji there
if (textEndX + minEmojiSpace <= boxRight) {
emojiX = textEndX + 4;
emojiY = topY + lineHeight;
} else {
// Otherwise place emoji on a new line centered
needsNewLine = true;
emojiY = topY + lineHeight * 2;
}
} else {
// Only one line, place emoji after it
int textEndX = x1 + (line1.length() * charWidth);
// Check if there's enough space for emoji on the same line
if (textEndX + minEmojiSpace <= boxRight) {
emojiX = textEndX + 4;
emojiY = topY;
} else {
// Not enough space, place on the next line centered
needsNewLine = true;
emojiY = topY + lineHeight;
}
}
// Center the emoji horizontally if it's on its own line
if (needsNewLine) {
emojiX = boxLeft + (boxWidth - emojiWidth) / 2; // Center the emoji in the box
}
// Final check to ensure emoji stays within bounds
if (emojiX + emojiWidth > boxRight) {
// If still out of bounds, force placement at center of next line
needsNewLine = true;
emojiX = boxLeft + (boxWidth - emojiWidth) / 2;
emojiY += lineHeight;
}
// Only draw emoji if both the top and bottom of the emoji are within visible area AND below cutoff
if (emojiY >= safeCutoffY && emojiY + emojiHeight <= maxVisibleY) {
drawEmoji(emojiIndex, emojiX, emojiY);
}
}
/**
* Draws the specified emoji at the given coordinates
* Only draws if position is below the safety cutoff line
*/
void drawEmoji(int emojiIndex, int x, int y) {
// Only draw emojis if they're below the safe cutoff line
if (y < safeCutoffY) return;
// Draw appropriate emoji based on index
const unsigned short BACKGROUND = 0xFFFF;
switch(emojiIndex) {
case 0: // heart
drawBitmapWithBackground(x, y, 16, 16, heart, BACKGROUND);
break;
case 1: // smile
drawBitmapWithBackground(x, y, 16, 16, smile, BACKGROUND);
break;
case 2: // flower
drawBitmapWithBackground(x, y, 16, 16, flower, BACKGROUND);
break;
case 3: // laugh
drawBitmapWithBackground(x, y, 16, 16, laugh, BACKGROUND);
break;
case 4: // rainbow
drawBitmapWithBackground(x, y, 16, 16, rainbow, BACKGROUND);
break;
}
}
/**
* Helper function to wrap an index around boundaries
* Used for cycling through options when reaching the end
*/
int wrapIndex(int index, int max) {
if (index < 0) return max - 1;
if (index >= max) return 0;
return index;
}
/**
* Adds the currently selected message to the message array
* Adds both text and emoji as separate message items
*/
void selectMessage() {
// Add the selected message to our array
if (messageCount < MAX_MESSAGES) {
// Add the text as a message item
messages[messageCount].text = options[selectedOption];
messages[messageCount].emojiIndex = optionEmojis[selectedOption];
messages[messageCount].isEmoji = false; // It's text
messageCount++;
// Add the emoji as a message item
if (messageCount < MAX_MESSAGES) {
messages[messageCount].text = "";
messages[messageCount].emojiIndex = optionEmojis[selectedOption];
messages[messageCount].isEmoji = true; // It's an emoji
messageCount++;
}
}
updateMessageDisplay();
}
/**
* Prints the currently selected message to the thermal printer
* Displays emojis on screen but prints them as text symbols
*/
void printSelectedMessage() {
printer.feed(1);
printer.println(F(" "));
printer.println(F("To: _________________"));
printer.println(F(" "));
printer.println(F(" "));
// Create a combined message string including text and emoji symbols
String combinedMessage = "";
// Process all messages
for (int i = 0; i < messageCount; i++) {
if (messages[i].isEmoji) {
// This is an emoji - add the text symbol for it
int emojiIdx = messages[i].emojiIndex;
if (emojiIdx >= 0 && emojiIdx < numOptions) {
combinedMessage += emojiSymbols[emojiIdx];
combinedMessage += " "; // Add space after emoji
}
} else {
// This is text - add it with a space
if (combinedMessage.length() > 0) {
combinedMessage += " "; // Add space between messages
}
combinedMessage += messages[i].text;
}
}
// Characters per line for the thermal printer
const int CHARS_PER_LINE = 32;
// Process the message with proper word wrapping
int currentPos = 0;
while (currentPos < combinedMessage.length()) {
// Calculate how many characters we can fit on this line
int charsThisLine = min(CHARS_PER_LINE, combinedMessage.length() - currentPos);
// If we're breaking in the middle of the line, find the last space
if (currentPos + charsThisLine < combinedMessage.length()) {
// Find the last space in this segment
int lastSpace = combinedMessage.lastIndexOf(' ', currentPos + charsThisLine);
// If we found a space, break there
if (lastSpace > currentPos) {
charsThisLine = lastSpace - currentPos;
}
}
// Check if we're about to break an exclamation point
if (currentPos + charsThisLine < combinedMessage.length() &&
combinedMessage.charAt(currentPos + charsThisLine) == '!') {
// Include the exclamation point on this line
charsThisLine++;
}
// Print this segment of the message
printer.println(combinedMessage.substring(currentPos, currentPos + charsThisLine));
// Move to the next position (skip the space we broke at)
if (currentPos + charsThisLine < combinedMessage.length() &&
combinedMessage.charAt(currentPos + charsThisLine) == ' ') {
currentPos = currentPos + charsThisLine + 1;
} else {
currentPos = currentPos + charsThisLine;
}
}
printer.println(F(" "));
printer.println(F(" "));
printer.println(F(" From: _________________"));
printer.println(F(" "));
printer.feed(2);
// Print success message on display
myGLCD.setColor(255, 255, 255);
myGLCD.fillRect(240, 250, 479, 300);
myGLCD.setColor(0, 0, 0);
myGLCD.setBackColor(255, 255, 255);
myGLCD.print("Message printed!", 270, 270);
// Clear all messages after printing
messageCount = 0;
updateMessageDisplay();
delay(2000);
myGLCD.setColor(255, 255, 255);
myGLCD.fillRect(240, 250, 479, 300);
}
/**
* Setup function - initializes hardware and draws initial interface
*/
void setup() {
Serial.begin(9600);
// Initialize the printer
pinMode(7, OUTPUT); digitalWrite(7, LOW);
pinMode(printButtonPin, INPUT_PULLUP);
mySerial.begin(19200);
printer.begin();
// Initialize the display
myGLCD.InitLCD();
myGLCD.setFont(BigFont);
myGLCD.clrScr();
// Set up input pins
pinMode(potPin, INPUT);
pinMode(buttonPin, INPUT_PULLUP);
// Draw initial interface
drawInterface();
updateOptionDisplay();
}
/**
* Main loop - handles input and updates display
*/
void loop() {
// Read potentiometer and map to options
int newPotValue = analogRead(potPin);
int newOption = map(newPotValue, 0, 1023, 0, numOptions);
// Check for significant change in the potentiometer value
if (abs(newPotValue - potValue) > (10 * potSensitivity)) {
potValue = newPotValue;
// Reverse scrolling direction (decrement instead of increment)
if (newOption >= numOptions) newOption = numOptions - 1;
if (newOption < 0) newOption = 0;
selectedOption = numOptions - 1 - newOption;
// Only update if the selected option changes
if (selectedOption != previousSelectedOption) {
updateOptionDisplay();
}
}
// Handle display interface button
int reading = digitalRead(buttonPin);
if (reading != lastButtonState) lastDebounceTime = millis();
if ((millis() - lastDebounceTime) > debounceDelay) {
if (reading != buttonState) {
buttonState = reading;
if (buttonState == LOW) selectMessage();
}
}
lastButtonState = reading;
// Handle printer button
int printReading = digitalRead(printButtonPin);
if (printReading != lastPrintButtonState) lastPrintDebounceTime = millis();
if ((millis() - lastPrintDebounceTime) > debounceDelay) {
if (printReading != printButtonState) {
printButtonState = printReading;
if (printButtonState == LOW) printSelectedMessage();
}
}
lastPrintButtonState = printReading;
delay(30);
}