Introduction
This project was completed as part of my robotics class during the spring semester of my junior year at Tufts. The goal for this project was to design something that demonstrated the capabilities of a new microcontroller released in 2020, the WIO Terminal.
The WIO is compatible with both Arduino and MicroPython software languages, but I opted to use Arduino given my familiarity with coding in that language. Through a set of GPIO pins, a variety of devices can be interfaced with the WIO for all matter of projects. An Arduino compatible processor and GPIO pins are commonplace among microcontrollers though and the WIO terminal has so much more to offer. The most distinctive feature is the large liquid crystal display (LCD) screen taking up an entire face of the WIO's slim box shape. Beyond that, the WIO also contains an inertial measurement unit (IMU), micrphone, buzzer, real time clock (RTC) module, configurable buttons, bluetooth connectivity, WiFi connectivity, light sensor, and IR emitter. All these details are documented on the Seeed Wiki.
Alarm Clock
To simultaneously demonstrate many of the WIO's unique features through one project I decided to transform the WIO into a small alarm clock. Using the RTC module, the WIO keeps track of the time. The WIO then displays the time in either an analog or digital format on the LCD screen. By pressing the configurable buttons, one can switch between analog or digital time displays, and also set alarms to go off later. When the alarm time is reached, the buzzer alternates between low and high frequencies in an annoying way, only turning off after any of the buttons have been pressed. A thermistor in a voltage divider circuit with a resistor on the GPIO pins also keeps track of the room temperature to be displayed on the LCD.
Alarm Clock Demonstration
Arduino Code
/*
Author: Rónán Gissler
Date: 1/30/21
The below code makes extensive use of code provided in examples on the WIO terminal
wiki, including the buzzer melody example, RTC example, TFT_Clock example and
other more basic examples on graphics and button control.
The thermistor code was adapted from Example #3 from the Adafruit Learning System
Guide on Thermistors.
To execute this program in full on your own WIO terminal, you will need to plug
in a 10K Ohm resistor and thermistor into the GPIO pins on the back of the WIO.
*/
#include "TFT_eSPI.h"
TFT_eSPI tft;
#include "RTC_SAMD51.h"
#include "DateTime.h"
RTC_SAMD51 rtc;
//background color on screen
#define BACKGROUND_CLR 0x5AAA
//boolean used to set analog or digital time display
boolean analog = true;
// sec, min, hr hand angles
float sdeg, mdeg, hdeg;
// sec, min, hr hand angle multipliers for positioning of tip
float sx, sy, mx, my, hx, hy;
//start and end point coordinates of lines on clock face
uint16_t xI, yI, xF, yF;
//coordinates of points on clock face
uint16_t xP, yP;
//size scale of clock allowing for convenient size adjustment
float clockScale = 1;
//origin coordinates of clock
int clockX = 110;
int clockY = 105;
//tip of sec, min, hr hand X-coordinates
uint16_t osx = clockX, omx = clockX, ohx = clockX;
//tip of sec, min, hr hand Y-coordinates
uint16_t osy = clockY, omy = clockY, ohy = clockY;
//current time: sec, min, hr
int ss, mm, hh;
//current time: sec, min, hr as strings
String ssStr, mmStr, hhStr;
//boolean used to draw clock body once
boolean initial = true;
//bolean used to turn off alarm
boolean snooze = false;
//used to turn on alarm setting mode and change which value is being set
int alarmSetting = 0;
//alarm time: sec, min, hr
int mmA = 0, hhA = 0;
//alarm time: sec min, hr as strings
String mmAstr, hhAstr;
// which analog pin to connect
#define THERMISTORPIN A8
// resistance at 25 degrees C
// the value of the 'other' resistor
#define SERIESRESISTOR 10000
// how many samples to take and average, more takes longer
// but is more 'smooth'
#define NUMSAMPLES 10
//The below information was collected from the datasheet for the FocuSens
//MF52D-103f-3950 NTC thermistor, except the thermistor's resistance which
//was varied to bring the reported temperatures close to another temp. sensor
//used as a reference.
// nominal resistance of thermistor
#define THERMISTORNOMINAL 9000 //had value 103 (what units??) on datasheet
// temp. for nominal resistance
#define TEMPERATURENOMINAL 25
// The beta coefficient of the thermistor
#define BCOEFFICIENT 3950
//array of sampled temperatures to be averaged
int samples[NUMSAMPLES];
//counter used to determine the rate at which the temperature updates
int tempCount = 0;
//counter used to determine rate of time flashing when setting alarm
int alarmCount = 0;
void setup() {
//initiliaze an instance of the TFT LCD libary object
tft.begin();
//select the corner of the LCD from which to start counting pixels,
//in effect rotating the display
tft.setRotation(3); //a value from 0 to 3
tft.fillScreen(BACKGROUND_CLR); //Gray background on LCD
drawBackground(); //Add background graphics
drawAlarm(false, false);
//initialze an instance of the RTC library object
rtc.begin();
//set RTC time using computer connected via serial communication
DateTime now = DateTime(F(__DATE__), F(__TIME__));
rtc.adjust(now);
now = rtc.now(); //check time
//setting GPIO pins and button pins
pinMode(WIO_KEY_A, INPUT_PULLUP);
pinMode(WIO_KEY_B, INPUT_PULLUP);
pinMode(WIO_KEY_C, INPUT_PULLUP);
pinMode(THERMISTORPIN, INPUT);
if (analog){
drawClock();
}
}
void loop() {
DateTime now = rtc.now(); //recheck time
ss = now.second();
mm = now.minute();
hh = now.hour();
if (tempCount%50 == 0){ //condition met every 30 cycles of the loop
drawTemp(); //checks the current temperature and displays it on LCD
}
tempCount++;
if (digitalRead(WIO_KEY_A) == LOW && !alarmSetting) {
tft.fillScreen(BACKGROUND_CLR); //clear screen
drawBackground();
drawAlarm(false, false);
drawTemp();
initial = true;
analog = !analog; //flip display from analog to digital or vice versa
delay(200); // delay to avoid multiple button presses from being registered
}
if (digitalRead(WIO_KEY_C) == LOW) {
alarmSetting++;
delay(200); // delay to avoid multiple button presses from being registered
}
if (alarmSetting > 0){
if (alarmSetting == 1){
drawAlarm(true, true);
if (digitalRead(WIO_KEY_B) == LOW && hhA < 23) {
hhA++;
}
else if (digitalRead(WIO_KEY_A) == LOW && hhA > 0) {
hhA--;
}
}
else if (alarmSetting == 2){
drawAlarm(true, false);
if (digitalRead(WIO_KEY_B) == LOW && mmA < 59) {
mmA++;
}
else if (digitalRead(WIO_KEY_A) == LOW && mmA > 0) {
mmA--;
}
}
else if (alarmSetting == 3){
alarmSetting = 0;
alarm();
}
}
if (analog) {
// Pre-compute hand degrees, x & y coords for a fast screen update
sdeg = ss * 6; // 0-59 -> 0-354
mdeg = mm * 6 + sdeg * 0.01666667; // 0-59 -> 0-360 - includes seconds
hdeg = hh * 30 + mdeg * 0.0833333; // 0-11 -> 0-360 - includes minutes and seconds
sx = cos((sdeg - 90) * 0.0174532925);
sy = sin((sdeg - 90) * 0.0174532925);
mx = cos((mdeg - 90) * 0.0174532925);
my = sin((mdeg - 90) * 0.0174532925);
hx = cos((hdeg - 90) * 0.0174532925);
hy = sin((hdeg - 90) * 0.0174532925);
// Redraw new hand positions
tft.drawLine(osx, osy, clockX, clockY, TFT_WHITE);
osx = sx * 89*clockScale + clockX;
osy = sy * 89*clockScale + clockY;
tft.drawLine(osx, osy, clockX, clockY, TFT_RED);
tft.drawLine(omx, omy, clockX, clockY, TFT_WHITE);
omx = mx * 80*clockScale + clockX;
omy = my * 80*clockScale + clockY;
tft.drawLine(omx, omy, clockX, clockY, TFT_BLACK);
tft.drawLine(ohx, ohy, clockX, clockY, TFT_WHITE);
ohx = hx * 56*clockScale + clockX;
ohy = hy * 56*clockScale + clockY;
tft.drawLine(ohx, ohy, clockX, clockY, TFT_BLACK);
drawClock(); //redraw dots and dashes on clock, otherwise they will
//slowly be erased as the hands are erased and replaced
delay(100); //small delay reduces flickering by preventing shapes from
//being drawn and erased in quick succession
}
//digital display
else {
ssStr = String(ss);
mmStr = String(mm);
hhStr = String(hh);
//add leading zero to second, minute, hour to prevent
//numbers from lingering on display at end of cycle
if (ssStr.length() == 1){
ssStr = "0" + ssStr;
}
if (mmStr.length() == 1){
mmStr = "0" + mmStr;
}
if (hhStr.length() == 1){
hhStr = "0" + hhStr;
}
tft.setTextSize(3);
tft.setTextColor(TFT_RED, BACKGROUND_CLR);
tft.drawString(hhStr + ":" + mmStr + ":" + ssStr, 38, 110);
}
}
void drawBackground(){
tft.setTextColor(TFT_BLACK);
tft.setTextSize(2);
tft.drawString("Current Time", 42, 212);
tft.drawString("Alarm", 230, 20);
tft.drawString("Temp.", 230, 120);
tft.setTextColor(TFT_RED);
}
void drawClock(){
if (initial) {
initial = false;
// Draw clock face
tft.fillCircle(clockX, clockY, 98*clockScale, TFT_BLUE);
tft.fillCircle(clockX, clockY, 90*clockScale, TFT_WHITE);
}
// Draw 12 lines
for (int i = 0; i < 360; i += 30) {
sx = cos((i - 90) * 0.0174532925);
sy = sin((i - 90) * 0.0174532925);
xI = sx * 90*clockScale + clockX;
yI = sy * 90*clockScale + clockY;
xF = sx * 80*clockScale + clockX;
yF = sy * 80*clockScale + clockY;
tft.drawLine(xI, yI, xF, yF, TFT_BLACK);
}
// Draw 60 dots
for (int i = 0; i < 360; i += 6) {
sx = cos((i - 90) * 0.0174532925);
sy = sin((i - 90) * 0.0174532925);
xP = sx * 82*clockScale + clockX;
yP = sy * 82*clockScale + clockY;
// Draw minute markers
tft.fillCircle(xP, yP, 1, TFT_BLACK);
// Draw main quadrant dots
if (i%90 == 0) {
tft.fillCircle(xP, yP, 2, TFT_BLACK);
}
}
tft.fillCircle(clockX, clockY, 3, TFT_BLACK); //draw center circle
}
void drawAlarm(boolean setting, boolean setHour){
tft.setTextSize(2);
tft.setTextColor(TFT_RED, BACKGROUND_CLR);
hhAstr = String(hhA);
mmAstr = String(mmA);
//add leading zero if time is single digit
if (hhAstr.length() == 1){
hhAstr = "0" + hhAstr;
}
if (mmAstr.length() == 1){
mmAstr = "0" + mmAstr;
}
if (setting && setHour && alarmCount%6 == 0){
tft.drawString(" ", 230, 70);
}
else if (setting && !setHour && alarmCount%6 == 0){
tft.drawString(hhAstr + ":" + " ", 230, 70);
}
else{
tft.drawString(hhAstr + ":" + mmAstr, 230, 70);
}
alarmCount++;
}
void alarm(){
DateTime now = rtc.now(); //recheck time
DateTime alarm = DateTime(now.year(), now.month(), now.day(), hhA, mmA, 0);
rtc.setAlarm(0,alarm); // match at alarm set time that day
rtc.enableAlarm(0, rtc.MATCH_HHMMSS); // match Every Day
rtc.attachInterrupt(alarmMatch); // callback whlie alarm is match
}
//code to sound alarm when alarm is set off
void alarmMatch(uint32_t flag){
int duration1 = 300;
int tone1 = 600;
int duration2 = 300;
int tone2 = 3200;
while (!snooze){
if (digitalRead(WIO_KEY_C) == LOW) {
snooze = true;
delay(200);
}
for (long i = 0; i < duration1 * 1000L; i += tone1 * 2) {
digitalWrite(WIO_BUZZER, HIGH);
delayMicroseconds(tone1);
digitalWrite(WIO_BUZZER, LOW);
delayMicroseconds(tone1);
}
delay(200);
for (long i = 0; i < duration1 * 1000L; i += tone2 * 2) {
digitalWrite(WIO_BUZZER, HIGH);
delayMicroseconds(tone2);
digitalWrite(WIO_BUZZER, LOW);
delayMicroseconds(tone2);
}
delay(100);
}
}
String checkTemp(){
uint8_t i;
float average;
// take N samples in a row, with a slight delay
for (i=0; i< NUMSAMPLES; i++) {
samples[i] = analogRead(THERMISTORPIN);
delay(10);
}
// average all the samples out
average = 0;
for (i=0; i< NUMSAMPLES; i++) {
average += samples[i];
}
average /= NUMSAMPLES;
// convert the value to resistance
average = 1023 / average - 1;
average = SERIESRESISTOR / average;
float steinhart;
steinhart = average / THERMISTORNOMINAL; // (R/Ro)
steinhart = log(steinhart); // ln(R/Ro)
steinhart /= BCOEFFICIENT; // 1/B * ln(R/Ro)
steinhart += 1.0 / (TEMPERATURENOMINAL + 273.15); // + (1/To)
steinhart = 1.0 / steinhart; // Invert
steinhart -= 273.15; // convert absolute temp to C
steinhart = (steinhart*9.0)/5.0 + 32.0; // convert celsius to fahrenheit
String tempF = String(steinhart).substring(0,4); //rounds temp to tenths place
return tempF;
}
void drawTemp(){
String currentTemp = checkTemp();
tft.setTextSize(2);
tft.setTextColor(TFT_RED, BACKGROUND_CLR);
tft.drawString(currentTemp + " *F", 220, 160);
}