Reminder Bear
by the Hydrangeas
by the Hydrangeas
Front view display. Right hand is holding up the microphone.
Back view access into inside and power supply. Speaker can be seen attached to the back of the head.
Teddy Alert is a reminder device built for our client Charmaine to help her in completing her day with ease and confidence. Identifying our problem space, we found that she struggles with memory and can feel less confident about herself due to her struggles with memory and difficulty in completing daily tasks. As a solution we decided to create this device as a way to remind Charmaine of her daily tasks in a way that is more companion like and friendly, adding character to the monotonous. To operate the device, it functions off of a clock based system, taking tasks in Charmaine's day and organizing them into a schedule.
The device has 4 primary screens, the default/idle screen, the alert screen, the affirmation screen and a settings/record screen. Beginning on the default screen, here can be seen the time and the date as well as a heart system along the top. At the time of day that Charmaine is to complete a tasks, a screen with the task title as well as an image assistant will appear to remind her of completing this task. To confirm that she has completed this task she can then press the left foot button of the bear with the heart painted on it to complete the task, to which she will then be directed to a new screen with a happy bear and congratulation message. At this point she will also hear the recording of an affirmation previously recorded on the bear play.
After the recording finishes, it will return to the default clock screen and a new full heart will be seen to signify the number of tasks she has completed (one heart per task). In the process of recording a affirmation, the user can select the left gear button on the right foot of the bear to access the record affirmations screen. On this screen the display will have directions on the use of each button to record the affirmation. By pressing the left button with the gear, the user can start their recording. They will have 10 seconds to record their affirmation, and by pressing the right button can stop their recording at any point. By pressing both buttons the user can return to the home/default screen, and recordings will play in a random order after Charmaine completes a task on the affirmations screen.
Close up shots of the back of the bear. The circuit is held together inside a cardboard box, adapter included for power supply, can be attached to a battery.
Close up shots of the screen displays. On the left is the primary default screen when no alerts are active.
On the right is the recording affirmations screen, which is seen after selecting the settings gear button. On this screen you can record a sweet affirmation to be played once our client has verified completion of a task.
Left foot and right foot buttons of the bear painted with a heart and gear to symbolize the different and primary features of the button.
The heart button is pressed to confirm the action of completing a task, hence filling a heart on the display, and the left button is pressed to access the settings/record screen.
On the record affirmations screen, the display has written directions for the use of each button. The left button is used to start recording, after which you have 10 seconds to make a recording, and the right button is used to stop a recording. When both buttons are pressed, this returns the user back to the home default screen.
This video on the left is a demonstration of the recording screen function.
Gamified heart system along the top of the screen to mark task completion.
DESIGNING UI
Some ideas for what the reminder screens would look like. The heart system was added as a playful way of showing how many tasks have been completed.
After receiving the schedule for repeated tasks in Charmaine's day, we worked to organize these into a weekly basis and map it into the UI screens for the device.
As we mapped the flow of the screens, we came to the error with the touch screen, finding the use of buttons would be more easily intuitive as well as better feeling in the nature of the bear device.
As we progressed in the project, due to time constraints, we also removed the ability to add or remove tasks in the day, and simplified the recording screen to be all in a single screen in order to still maintain the general concept.
SOFTWARE/HARDWARE DEVELOPMENT
The Teensy audio system before a proper speaker module was added (you can see the headphone jack on the bottom of the Teensy controller, which was connected to an externally powered speaker system. The small component on the far left is the microphone, and the three buttons on the right provide record/stop/play functionality.
The three voltage regulators used to take 5V digital signals sent from the Arduino to the 3.3V digital pins of the Teensy.
An early iteration of the Teensy audio system. Here, on the top right, a 12V amplifier is being used for audio output, since the amplifier attached to the unattached speaker on the right was not working (likely had been accidentally supplied too much voltage at some earlier point).
The full circuit before soldering, and before replacing the Arduino Uno with the Arduino Mega (you can see the outline of the wingshield we used with the Uno under the touchscreen in the bottom right).
The final configuration of the soldered components before they were inserted into the bear. The prototype board that each of the power/ground wires were soldered to is tucked into the Arduino Mega in the space next to the touchscreen. On the top, you can see the two button components that would be attached inside the bear to the physical arcade pushbuttons.
PHYSICAL FABRICATION
Early drawings and drafts of the bear and planning dimensions of the internal structure, as well as placement of the screen and possible UI concepts. Also some drawings of the software and ideation of the functions with and without screens.
Process on assembly of the bear and measurements. As we measured parts of the software, we could start to attach and understandt the wiring and placement in the bear. We found that the bear needed more space inside, so we attached more material to the belly to fit the dimensions of the screen, as well as sewing in a zipper in the back for easy access to the internal parts, and battery. We later then attached the microphone to the hand and lifted it to face the direction of the user, to make the recordings more user centric and clearer.
A demo of the Teensy/Arduino system working. I record a short clip through the affirmation screen, then watch as a reminder is triggered by an alarm to play the affirmation.
In this video, the bear is in demo mode to showcase the appearance of the alarm screens. The Bathroom, Shower, and Brush Teeth alarms are shown here in quick succession (not related to actual time for demonstration).
One positive critique that we received during the in-class crit was that we did a great job with the soft fabrication side of our build. In particular, the critiquers were impressed by how everything was protected by the internal box and the how the hardware didn't jiggle around much. We were also complimented on the sewing job of the zipper onto the back of the bear. We definitely appreciate their critques, but we would have loved to replace the internal cardboard box with a 3D printed one. That was our original plan, but due to time constraints we had to pivot in order to meet the deadline.
One negative critique that we received during the in-class crit was that our buttons were not programmed appropriately because the critiquer's first instinct was to spam the buttons. Although we understand that we should have accounted for that inclination, because the bear needs to be reactive to actual inputs, there is no good way to account for that inclination. The button action mapping was very deliberate, so we didn't want to make it hard for Charmaine to get a reaction from pressing the buttons.
We are happy with how the project came out. Going into this project, we intended to solve a design problem of how to incorporate soft fabrication with physical computing in order to create a friendly look. We learned a lot about how to approach working with textiles and stuffing, as well as about how to manage projects that use multiple microcontrollers. Although we did spend a lot of our work time waiting on parts to arrive, we are proud of the fact that we were able to pull through and have a final, working project for Charmaine. We were able to separate our tasks such that each person had a different role to fulfill: Jaden worked on the audio with the Teensy, Michelle worked on the graphics with the Arduino Mega, and Isabel worked on the UI design and final construction. At the end, we combined all of the parts to create a cohesive, well crafted final project.
We learned that working with soft fabrication is a completely different challenge than the fabrication that we have done until now. The biggest challenge was figuring out how to fit all of the hardware into the bear. We had to take measurements to make sure that the box is the correct dimensions to both fit the box into the bear as well as fit all of the components.
Another lesson that we learned from this process was that the Arduino Uno does not work well for larger code projects. Since the screen we used plugs into every single port on the Uno, we had to order a wingshield in order to access the ports that were not actually being used. Towards the end of our project, we hit a road block where the program itself was simply too large for the Uno's memory. To counteract this problem, we switched from the Arduino Uno to the Arduino Mega. Aside from the larger memory space on the Mega, an added benefit of the switch was that the Mega has many more digital pins available than the Uno. We were then able to take off the wingshield, which reduced the size of the hardware quite a bit.
Working with multiple microcontrollers is also a difficult task. Once we got the Teensy and the Arduino Mega working separately, we needed to figure out how to control the signals from the Mega to the Teensy such that signals didn't cause the Teensy to do the same thing multiple times when buttons were pressed.
If we were to build another iteration of this project, we would like to make the whole project battery powered so that it can be carried around during the day. Similarly, we would want to add a velcro strap so that the bear can strap onto Charmaine's walker when she doesn't want to carry it in her arms.
/*
Project Title: Reminder Bear
Author: Michelle Chen, Jaden Singh
Description: The Reminder Bear has preset alarms that remind Charmaine of tasks throughout her day. If an alarm is not going off, the user can click the left button to reach the affirmation recording screen. To record an affirmation, the user can click the left button again to activate a ten second countdown during which they can record a message. If they want to end the message early, they can click the right button. If an alarm is going off, Charmaine can click the right button to mark the task as complete, which plays the affirmation message and adds a heart to the heart bar. Once all tasks are complete for the day, the heart bar will be full!
Code written for an Arduino Mega
pin mapping:
Arduino pin | role | description
___________________________________________________________________
4 output microSD pin for the screen
9, 19, SPI output display pins of the screen
20 input SDA pin for the RTC
21 input SCL pin for the RTC
31 input button on the bear's right foot
38 output signal to the Teensy for recording audio
41 output signal to the Teensy for stopping the recording
42 input button on the bear's left foot
46 output signal to the Teensy for playing the recording
Code released to the public domain by the author, 04/28/3035
Michelle Chen, mfchen@andrew.cmu.edu
Jaden Singh, jadens@andrew.cmu.edu
*/
/*
* Some of the below code is adapted from the TimeAlarmExample from TimeAlarms library created by Michael Margolis
* and the graphicstest example code from Adafruit's ILI9341 library
*/
#include <TimeLib.h>
#include <TimeAlarms.h>
#include <Wire.h>
#include <DS1307RTC.h> // a basic DS1307 library that returns time as a time_t
// libraries for the screen
#include <Adafruit_GFX.h>
#include <SPI.h>
#include <Adafruit_ILI9341.h>
#include <Adafruit_TSC2007.h>
#include <SdFat.h>
#include <Adafruit_SPIFlash.h>
#include <Adafruit_ImageReader.h>
// library to handle button events
#include <Bounce2.h>
#define USE_SD_CARD
// The display also uses hardware SPI, plus #9 & #10
#define TFT_CS 10
#define TFT_DC 9
#define SD_CS 4 // SD card select pin
// The Adafruit_ImageReader constructor call (above, before setup())
// accepts an uninitialized SdFat or FatVolume object. This MUST
// BE INITIALIZED before using any of the image reader functions!
#if defined(USE_SD_CARD)
SdFat SD; // SD card filesystem
Adafruit_ImageReader reader(SD); // Image-reader object, pass in SD filesys
#else
// SPI or QSPI flash filesystem (i.e. CIRCUITPY drive)
#if defined(__SAMD51__) || defined(NRF52840_XXAA)
Adafruit_FlashTransport_QSPI flashTransport(PIN_QSPI_SCK, PIN_QSPI_CS,
PIN_QSPI_IO0, PIN_QSPI_IO1, PIN_QSPI_IO2, PIN_QSPI_IO3);
#else
#if (SPI_INTERFACES_COUNT == 1)
Adafruit_FlashTransport_SPI flashTransport(SS, &SPI);
#else
Adafruit_FlashTransport_SPI flashTransport(SS1, &SPI1);
#endif
#endif
Adafruit_SPIFlash flash(&flashTransport);
FatVolume filesys;
Adafruit_ImageReader reader(filesys); // Image-reader, pass in flash filesys
#endif
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);
Adafruit_Image img; // An image loaded into RAM
int32_t width = 0, // BMP image dimensions
height = 0;
const char *monthName[12] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
// list of all the heart screen files
const char *heartScreens[10] = {
"/00.bmp", "/01.bmp", "/02.bmp", "/03.bmp", "/04.bmp", "/05.bmp", "/06.bmp",
"/07.bmp", "/08.bmp", "/09.bmp"
};
Bounce2::Button leftButton = Bounce2::Button();
Bounce2::Button rightButton = Bounce2::Button();
tmElements_t tm;
int lastDisplayedMinute = -1;
// 0 = idle screen, 1 = alarm screen, 2 = task done screen, 3 = recording screen
int screenStatus = 0;
// 0 = not recording, 1 = recording
int recordState = 0;
// number of hearts maxes out at 9
int numHearts = 0;
// for drawing on the record screen
int boxW = 84,
boxH = 60,
boxX = (tft.height() - boxW) / 2 + 3,
boxY = (tft.width() - boxH) / 2 - 5,
triLeftX = boxX + boxW / 4,
triTopY = boxY + boxH / 4,
triBottomY = boxY + (3 * boxH) / 4,
triTipX = boxX + (3 * boxW) / 4,
triMidY = boxY + boxH / 2;
const int PLAYPIN = 46,
STOPPIN = 41,
RECORDPIN = 38,
LEFTBUTTONPIN = 42,
RIGHTBUTTONPIN = 31;
void setup() {
ImageReturnCode stat; // Status from image-reading functions
// set up buttons & signal pins
pinMode(PLAYPIN, OUTPUT);
pinMode(STOPPIN, OUTPUT);
pinMode(RECORDPIN, OUTPUT);
leftButton.attach(LEFTBUTTONPIN, INPUT_PULLUP);
rightButton.attach(RIGHTBUTTONPIN, INPUT_PULLUP);
leftButton.interval(5);
rightButton.interval(5);
leftButton.setPressedState(HIGH);
rightButton.setPressedState(HIGH);
// set up screen
tft.begin();
tft.setRotation(3);
tft.setTextColor(ILI9341_WHITE);
// set up rtc
bool parse=false;
bool config=false;
while (!Serial) ; // wait for Arduino Serial Monitor
delay(200);
if (parse && config) {
Serial.print("DS1307 configured Time=");
Serial.print(__TIME__);
Serial.print(", Date=");
Serial.println(__DATE__);
} else if (parse) {
Serial.println("DS1307 Communication Error :-{");
Serial.println("Please check your circuitry");
} else {
Serial.print("Could not parse info from the compiler, Time=\"");
Serial.print(__TIME__);
Serial.print("\", Date=\"");
Serial.print(__DATE__);
Serial.println("\"");
}
setSyncProvider(RTC.get); // sync alarm library w RTC time
showHomeScreen();
// bathroom alarms
Alarm.alarmRepeat(23,30,0, bathroomAlarm); // 11:30pm every day
Alarm.alarmRepeat(3,30,0, bathroomAlarm); // 3:30am every day
Alarm.alarmRepeat(6,30,0, bathroomAlarm); // 6:30am every day
// teeth brushing + face washing alarms
Alarm.alarmRepeat(6,45,0, teethAlarm); // 6:45am every day
Alarm.alarmRepeat(19,0,0, teethAlarm); // 7:00pm every day
// medication alarms
Alarm.alarmRepeat(7,45,0, mornMedAlarm); // 7:45am every day
Alarm.alarmRepeat(12,0,0, noonMedAlarm); // 12:00pm every day
Alarm.alarmRepeat(19,0,0, eveMedAlarm); // 7:00pm every day
// reset the heart count at midnight
Alarm.alarmRepeat(0,0,0, resetHearts);
}
void loop() {
digitalWrite(RECORDPIN, 1);
digitalWrite(PLAYPIN, 1);
digitalWrite(STOPPIN, 1);
leftButton.update();
rightButton.update();
// if we are on the idle screen:
if(screenStatus == 0){
// update screen time every minute
if (minute() != lastDisplayedMinute) {
// draw the idle screen
showHomeScreen();
lastDisplayedMinute = minute(); // store current minute
}
if(leftButton.fell()){
screenStatus = 3;
recordState = 0;
reader.drawBMP("/Record.bmp", tft, 0, 0);
tft.fillRect(boxX, boxY, boxW, boxH, ILI9341_PURPLE);
tft.fillTriangle(triLeftX, triTopY, triLeftX, triBottomY, triTipX, triMidY, ILI9341_WHITE);
}
}
// if we are on an alarm screen:
else if(screenStatus == 1){
if(rightButton.fell()){
taskComplete(); // takes you to task complete screen
}
}
// if we are on the recording screen:
else if(screenStatus == 3){
// register if we want to go home instead of record:
// if not recording, start recording:
if((digitalRead(LEFTBUTTONPIN) == HIGH) && (digitalRead(RIGHTBUTTONPIN) == HIGH)){
//shouldn't care about recordState
digitalWrite(STOPPIN, 0);
digitalWrite(RECORDPIN, 1);
digitalWrite(PLAYPIN, 1);
delay(1000); //stop, in case something is being recorded
screenStatus = 0;
showHomeScreen();
}
if(recordState == 0 && leftButton.fell()){
recordState = 1;
digitalWrite(RECORDPIN, 0);
digitalWrite(PLAYPIN, 1);
digitalWrite(STOPPIN, 1);
delay(1000);
recordCountDown();
}
// if already recording: stop
else if(recordState == 1 && rightButton.fell()){
Serial.println("Stop recording");
recordState = 0;
digitalWrite(STOPPIN, 0);
digitalWrite(RECORDPIN, 1);
digitalWrite(PLAYPIN, 1);
delay(1000);
//go to home
screenStatus = 0;
showHomeScreen();
}
}
if(dowSunday){
Alarm.alarmOnce(13, 30, 0, showerAlarm);
}
else if((dowMonday || dowWednesday) || dowFriday){
Alarm.alarmOnce(7, 0, 0, showerAlarm);
}
else if(dowTuesday || dowThursday){
Alarm.alarmOnce(7,0,0, washFaceAlarm);
}
else{ // saturday
Alarm.alarmOnce(13, 30, 0, showerAlarm);
}
Alarm.delay(0);
}
// functions to be called when an alarm triggers
void bathroomAlarm(){
screenStatus = 1; // alarm screen status
reader.drawBMP("/Bathroom.bmp", tft, 0, 0);
// show heart status
reader.drawBMP(heartScreens[numHearts], tft, 0, 0);
digitalWrite(STOPPIN, 0); // play alarm sound
digitalWrite(RECORDPIN, 0);
digitalWrite(PLAYPIN, 0);
delay(5000);
digitalWrite(RECORDPIN, 1); // Stop alarm sound
digitalWrite(PLAYPIN, 1);
delay(1000);
}
void teethAlarm(){
screenStatus = 1; // alarm screen status
reader.drawBMP("/Brush_teeth.bmp", tft, 0, 0);
// show heart status
reader.drawBMP(heartScreens[numHearts], tft, 0, 0);
digitalWrite(STOPPIN, 0);
digitalWrite(RECORDPIN, 0);
digitalWrite(PLAYPIN, 0);
delay(5000);
digitalWrite(RECORDPIN, 1); // Stop alarm sound
digitalWrite(PLAYPIN, 1);
delay(1000);
}
void mornMedAlarm(){
screenStatus = 1; // alarm screen status
reader.drawBMP("/Morning_Pills.bmp", tft, 0, 0);
// show heart status
reader.drawBMP(heartScreens[numHearts], tft, 0, 0);
digitalWrite(STOPPIN, 0);
digitalWrite(RECORDPIN, 0);
digitalWrite(PLAYPIN, 0);
delay(5000);
digitalWrite(RECORDPIN, 1); // Stop alarm sound
digitalWrite(PLAYPIN, 1);
delay(1000);
}
void noonMedAlarm(){
screenStatus = 1; // alarm screen status
reader.drawBMP("/Noon_Pills.bmp", tft, 0, 0);
// show heart status
reader.drawBMP(heartScreens[numHearts], tft, 0, 0);
digitalWrite(STOPPIN, 0);
digitalWrite(RECORDPIN, 0);
digitalWrite(PLAYPIN, 0);
delay(5000);
digitalWrite(RECORDPIN, 1); // Stop alarm sound
digitalWrite(PLAYPIN, 1);
delay(1000);
}
void eveMedAlarm(){
screenStatus = 1; // alarm screen status
reader.drawBMP("/Evening_Pills.bmp", tft, 0, 0);
// show heart status
reader.drawBMP(heartScreens[numHearts], tft, 0, 0);
digitalWrite(STOPPIN, 0);
digitalWrite(RECORDPIN, 0);
digitalWrite(PLAYPIN, 0);
delay(5000);
digitalWrite(RECORDPIN, 1); // Stop alarm sound
digitalWrite(PLAYPIN, 1);
delay(1000);
}
void showerAlarm(){
reader.drawBMP("/Shower.bmp", tft, 0, 0);
screenStatus = 1; // alarm screen status
// show heart status
reader.drawBMP(heartScreens[numHearts], tft, 0, 0);
digitalWrite(STOPPIN, 0);
digitalWrite(RECORDPIN, 0);
digitalWrite(PLAYPIN, 0);
delay(5000);
digitalWrite(RECORDPIN, 1); // Stop alarm sound
digitalWrite(PLAYPIN, 1);
delay(1000);
}
void washFaceAlarm(){
Serial.print("OK! FACE!");
reader.drawBMP("/Wash_Face.bmp", tft, 0, 0);
screenStatus = 1; // alarm screen status
// show heart status
reader.drawBMP(heartScreens[numHearts], tft, 0, 0);
digitalWrite(STOPPIN, 0);
digitalWrite(RECORDPIN, 0);
digitalWrite(PLAYPIN, 0);
delay(5000);
digitalWrite(RECORDPIN, 1); // Stop alarm sound
digitalWrite(PLAYPIN, 1);
delay(1000);
}
void taskComplete(){
screenStatus = 2; // update screen status
reader.drawBMP("/Affirmation.bmp", tft, 0, 0);
unsigned long startTime;
// play the affirmation
startTime = millis();
digitalWrite(RECORDPIN, 1);
digitalWrite(PLAYPIN, 0);
digitalWrite(STOPPIN, 1);
delay(1000);
while(millis() <= startTime + 10000){
// send back to idle screen
//just wait for affirmation to hang out (do nothing)
}
//stop the affirmation
digitalWrite(STOPPIN, 0);
digitalWrite(PLAYPIN, 1);
digitalWrite(RECORDPIN, 1);
delay(1000);
//set back to idle home screen
screenStatus = 0;
numHearts++; // update heart count
Serial.print("Hearts: ");
Serial.println(numHearts);
showHomeScreen();
lastDisplayedMinute = minute(); // store current minute
}
char * screenTime() {
int hr = hour();
int displayHour = hr % 12;
if (displayHour == 0) displayHour = 12;
const char* ampm = (hr < 12) ? "AM" : "PM";
static char timeStr[10];
sprintf(timeStr, "%02d:%02d %s", displayHour, minute(), ampm);
return(timeStr);
}
void showHomeScreen(){
reader.drawBMP("/idleScreen.bmp", tft, 0, 0);
// show date on screen
tft.setTextSize(2);
//tft.setCursor(20, 60);
tft.setCursor((tft.width())/5 - 20, tft.height()/3);
tft.println(__DATE__);
// show time on screen
tft.setTextSize(5);
tft.setCursor((tft.width())/5 - 20, tft.height()/2 - 10);
char * time = screenTime();
tft.println(time);
// show heart status
Serial.print("Hearts before sccreen: ");
Serial.println(numHearts);
reader.drawBMP(heartScreens[numHearts], tft, 0, 0);
}
void recordCountDown(){
tft.fillRect(boxX, boxY, boxW, boxH, ILI9341_PURPLE);
tft.fillTriangle(triLeftX, triTopY, triLeftX, triBottomY, triTipX, triMidY, ILI9341_WHITE);
unsigned long lastTimeChecked = millis();
int secondsLeft = 10;
while(secondsLeft > 0){
if(millis() >= lastTimeChecked + 1000){
tft.fillRect(boxX, boxY, boxW, boxH, ILI9341_PURPLE);
tft.setTextSize(5);
int cursorX = boxX + (boxW - 30) / 2;
int cursorY = boxY + (boxH - 40) / 2;
tft.setCursor(cursorX, cursorY);
tft.print(secondsLeft); // draw the countdown
secondsLeft = secondsLeft - 1;
lastTimeChecked = millis();
}
rightButton.update();
if(recordState == 1 && rightButton.fell()){
screenStatus = 0;
recordState = 0;
digitalWrite(STOPPIN, 0);
digitalWrite(RECORDPIN, 1);
digitalWrite(PLAYPIN, 1);
delay(1000);
showHomeScreen();
return;
}
}
tft.fillRect(boxX, boxY, boxW, boxH, ILI9341_PURPLE);
tft.setCursor(boxX + 30, boxY + 15);
tft.print("!");
screenStatus = 0;
recordState = 0;
digitalWrite(STOPPIN, 0);
digitalWrite(RECORDPIN, 1);
digitalWrite(PLAYPIN, 1);
delay(1000);
}
bool getTime(const char *str)
{
int Hour, Min, Sec;
if (sscanf(str, "%d:%d:%d", &Hour, &Min, &Sec) != 3) return false;
tm.Hour = Hour;
tm.Minute = Min;
tm.Second = Sec;
return true;
}
bool getDate(const char *str)
{
char Month[12];
int Day, Year;
uint8_t monthIndex;
if (sscanf(str, "%s %d %d", Month, &Day, &Year) != 3) return false;
for (monthIndex = 0; monthIndex < 12; monthIndex++) {
if (strcmp(Month, monthName[monthIndex]) == 0) break;
}
if (monthIndex >= 12) return false;
tm.Day = Day;
tm.Month = monthIndex + 1;
tm.Year = CalendarYrToTm(Year);
return true;
}
void resetHearts(){
numHearts = 0;
}
/*
Description: This program configures a Teensy (using an attached Teensy Audio Shield Rev D)
to receive digital signals from a separate microcontroller and record and play sound accordingly.
With the use of an SD card, this program enables the Teensy to record up to 10 raw audio files to the card and play them back at random through
the audio shield line out, with the likely use of an amplifier. The recording input is through the audio shield MIC pin.
Code written for a Teensy 4.1 using a Teensy Audio Shield Rev D
Pin mapping:
Teensy pin | role | description
___________________________________________________________________
33 INPUT` Read digital signal to initiate recording (HIGH when not in use)
34 INPUT Read digital signal to stop recording/play (HIGH when not in use)
35 INPUT Read digital signal to start playing a recording (HIGH when not in use)
This program is adapted from the Recorder.ino program written by Paul Stoffregen for the Teensy here:
https://github.com/PaulStoffregen/Audio/blob/master/examples/Recorder/Recorder.ino
*/
#include <Bounce2.h>
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
AudioInputI2S i2s2; //xy=105,63
AudioAnalyzePeak peak1; //xy=278,108
AudioRecordQueue queue1; //xy=281,63
AudioPlaySdRaw playRaw1; //xy=302,157
AudioOutputI2S i2s1; //xy=470,120
AudioConnection patchCord1(i2s2, 0, queue1, 0);
AudioConnection patchCord2(i2s2, 0, peak1, 0);
AudioConnection patchCord3(playRaw1, 0, i2s1, 0);
AudioConnection patchCord4(playRaw1, 0, i2s1, 1);
AudioControlSGTL5000 sgtl5000_1;
#define RECORDPIN 33
#define STOPPIN 34
#define PLAYPIN 35
// Bounce objects to easily and reliably read the buttons
Bounce buttonRecord = Bounce(RECORDPIN, 8);
Bounce buttonStop = Bounce(STOPPIN, 8); // 8 = 8 ms debounce time
Bounce buttonPlay = Bounce(PLAYPIN, 8);
// which input on the audio shield will be used?
const int myInput = AUDIO_INPUT_MIC;
// Use with Teensy 4.1 SD card slot
#define SDCARD_CS_PIN BUILTIN_SDCARD
#define SDCARD_MOSI_PIN 11 // not actually used
#define SDCARD_SCK_PIN 13 // not actually used
// Remember which mode we're doing
int mode = 0; // 0=stopped, 1=recording, 2=playing, 3=alarming
// The file where data is recorded
File frec;
void setup() {
// Configure the pushbutton pins
pinMode(RECORDPIN, INPUT_PULLUP);
pinMode(STOPPIN, INPUT_PULLUP);
pinMode(PLAYPIN, INPUT_PULLUP);
// Audio connections require memory, and the record queue
// uses this memory to buffer incoming audio.
AudioMemory(60);
// Enable the audio shield, select input, and enable output
sgtl5000_1.enable();
sgtl5000_1.inputSelect(myInput);
sgtl5000_1.volume(0.8);
sgtl5000_1.micGain(02);
sgtl5000_1.lineOutLevel(20);
// Initialize the SD card
SPI.setMOSI(SDCARD_MOSI_PIN);
SPI.setSCK(SDCARD_SCK_PIN);
if (!(SD.begin(SDCARD_CS_PIN))) {
// stop here if no SD card, but print a message
while (1) {
Serial.println("Unable to access the SD card");
delay(500);
}
}
Serial.println("Initialized recorder");
}
void loop() {
// First, read the buttons
buttonRecord.update();
buttonStop.update();
buttonPlay.update();
bool record = buttonRecord.fallingEdge();
bool play = buttonPlay.fallingEdge();
bool stop = buttonStop.fallingEdge();
if ((digitalRead(RECORDPIN) == 0) && (digitalRead(STOPPIN) == 0) && (digitalRead(PLAYPIN) == 0) && (mode != 3)) { // play alarm!
playAlarm();
mode = 3;
}
// Respond to button presses
if (record && !play && !stop) {
delay(200); // to check that it is not in an alarm state
if ((digitalRead(STOPPIN) == 1) && (digitalRead(PLAYPIN) == 1)) {
Serial.println("Record Button Press");
if (mode == 2) stopPlaying();
if (mode == 0) startRecording();
}
}
if (stop && !record && !play) {
delay(200);
if ((digitalRead(RECORDPIN) == 1) && (digitalRead(PLAYPIN) == 1))
Serial.println("Stop Button Press");
if (mode == 1) stopRecording();
if (mode == 2) stopPlaying();
}
if (play && !record && !stop) {
delay(200);
if ((digitalRead(RECORDPIN) == 1) && (digitalRead(STOPPIN) == 1)) {
Serial.println("Play Button Press");
if (mode == 1) stopRecording();
if (mode == 0) startPlaying();
}
}
// If we're playing or recording, carry on...
if (mode == 1) {
continueRecording();
}
if (mode == 2) {
continuePlaying();
}
if (mode == 3) {
continuePlaying();
}
}
void playAlarm() {
//if starting with unused SD card, an ALARM.RAW file must be manually uploaded
Serial.println("Playing Alarm");
playRaw1.play("ALARM.RAW");
mode = 2;
}
void startRecording() {
Serial.println("startRecording");
char buffer[15]; // Buffer size should be sufficient for the formatted string
//Only get here if every file exists
int random_selector = random(1, 11);
Serial.print("Random selection: ");
Serial.println(random_selector);
snprintf(buffer, sizeof(buffer), "RECORD_%d.RAW", random_selector);
if (SD.exists(buffer)) {
// The SD library writes new data to the end of the
// file, so to start a new recording, the old file
// must be deleted before new data is written.
SD.remove(buffer);
}
frec = SD.open(buffer, FILE_WRITE);
if (frec) {
queue1.begin();
mode = 1;
}
}
void continueRecording() {
if (queue1.available() >= 2) {
byte buffer[512];
// Fetch 2 blocks from the audio library and copy
// into a 512 byte buffer. The Arduino SD library
// is most efficient when full 512 byte sector size
// writes are used.
memcpy(buffer, queue1.readBuffer(), 256);
queue1.freeBuffer();
memcpy(buffer+256, queue1.readBuffer(), 256);
queue1.freeBuffer();
// write all 512 bytes to the SD card
//elapsedMicros usec = 0;
frec.write(buffer, 512);
// Uncomment these lines to see how long SD writes
// are taking. A pair of audio blocks arrives every
// 5802 microseconds, so hopefully most of the writes
// take well under 5802 us. Some will take more, as
// the SD library also must write to the FAT tables
// and the SD card controller manages media erase and
// wear leveling. The queue1 object can buffer
// approximately 301700 us of audio, to allow time
// for occasional high SD card latency, as long as
// the average write time is under 5802 us.
//Serial.print("SD write, us=");
//Serial.println(usec);
}
}
void stopRecording() {
Serial.println("stopRecording");
queue1.end();
if (mode == 1) {
while (queue1.available() > 0) {
frec.write((byte*)queue1.readBuffer(), 256);
queue1.freeBuffer();
}
frec.close();
}
mode = 0;
}
void startPlaying() {
Serial.println("startPlaying");
int play_selector = random(1, 11);
Serial.print("Random selection for play: ");
Serial.println(play_selector);
char buffer[15]; // Buffer size should be sufficient for the formatted string
// Use snprintf to format the string
snprintf(buffer, sizeof(buffer), "RECORD_%d.RAW", play_selector);
while (!(SD.exists(buffer))) {
play_selector = random(1, 11);
Serial.print("Random selection for play: ");
Serial.println(play_selector);
snprintf(buffer, sizeof(buffer), "RECORD_%d.RAW", play_selector);
}
playRaw1.play(buffer);
mode = 2;
}
void continuePlaying() {
if (!playRaw1.isPlaying()) {
playRaw1.stop();
mode = 0;
}
}
void stopPlaying() {
Serial.println("stopPlaying");
if (mode == 2) playRaw1.stop();
mode = 0;
}