/* LED Matrix Clock. is in a wood cabinet and has a 8x40 red dot-matrix display.
It is powered by an attached wall power supply.
It is programmed by a 6-pin connector with the GND(black) pin nearest the edge.
It has the following components:
1. A UNO-R3 compatible Arduino with a micro-SD socket.
2. A DS3231 RTC with CR2032 battery backup.
3. Five modules with 8x8 LED matrices driven by MAX7219s and daisy-chained.
4. A Pololu IR LIDAR distance measuring sensor for mode selection.
5. A CDS photocell for auto-dimming..
6. Two push-buttons for setting adjustments.
7. A micro SD drive SPI CS = 4
8. An Ethernet interface SPI CS = 10
*/
#include <GyverMAX7219.h> //Only library found with a getRotation function
#include <ds3231.h>#include <Wire.h>
#include <SD.h>
#define AM_W 5 // 5 матрицы (40 точки)
#define AM_H 1 // 1 матрицы (8 точек)
#define PC_PORT A0
#define UP_BUTTON 5
#define DOWN_BUTTON 6
#define LIDAR_PORT 3
#define PULSE_PORT 2
#define LIDAR_TIMEOUT 20000
#define MAX_DISTANCES 4
#define VIEW_TIME 2
#define SS 4
#include <Wire.h>
#include <SD.h>
#define AM_W 5 // 5 матрицы (40 точки)
#define AM_H 1 // 1 матрицы (8 точек)
#define PC_PORT A0
#define UP_BUTTON 5
#define DOWN_BUTTON 6
#define LIDAR_PORT 3
#define PULSE_PORT 2
#define LIDAR_TIMEOUT 20000
#define MAX_DISTANCES 4
#define VIEW_TIME 2
#define SS 4
MAX7219 < 5, 1, 9 > mtrx; //5 modules wide, 1 high, CS = 9
ts t; //ts is a struct findable in ds3231.h
char dow[22] = "SunMonTueWedThuFriSat";
char mchar[37] = "JanFebMarAprMayJunJlyAugSepOctNovDec";
const int threshold[6] = {16, 160, 418, 607, 785, 1024}; //for the photocell
const int zones[MAX_DISTANCES] = {45, 25, 12, 7}; //for getDistance
const char *filenames[] = { "/Commands.txt", "/Displays.txt", "/Intro.txt", "/Info.txt", "Stored.txt", "MoveAway.txt"};
bool sdInit;
unsigned int sysMode;
bool changed = false;
struct adjust_t {
int labelInx;
int upLimit;
int lowLimit;
};
uint8_t loopCount = 39; //For timeouts
adjust_t adjVals[] = { {5, 23, 0}, {8, 59, 0}};
char txBuf[31];
const char preamble[] = "14L1776t,G,";
void transmitDT(void) {
uint8_t checkCount = 0;
sprintf(txBuf, "%s%02u,%02u,%02u,%02u,", preamble, t.mon, t.mday, t.hour, t.min);
for (int i = 11; i < 22; i++) {
checkCount += txBuf[i];
}
sprintf((txBuf + 23), "%02X", checkCount);
txBuf[25] = 0;
digitalWrite(PULSE_PORT, HIGH);
delay(20);
digitalWrite(PULSE_PORT, LOW);
delay(10);
Serial.println(txBuf);
}
void checkSD(int fnInx) {
File myFile;
char item = '~';
short unsigned int hourVal;
short unsigned int minuteVal;
char cmdVal;
char nameVal[7];
char actionVal[7];
int inx;
if (sdInit) {
myFile = SD.open(filenames[fnInx]);
}
else {
sdInit = SD.begin(SS);
Serial.print(fnInx);
Serial.println(" failed");
mtrx.println("failed");
mtrx.update();
delay(1000);
}
if (myFile) {
while ((myFile.available()) && (item > 3)) {
inx = 0;
do {
item = myFile.read();
if (item >= ' ') {
txBuf[inx++] = item;
}
} while ((item != '\n') && (item > 3));
txBuf[inx] = 0;
sscanf(txBuf, "%2hu %2hu %c %s %s", &hourVal, &minuteVal, &cmdVal, nameVal, actionVal);
if ((hourVal == t.hour) && (minuteVal == t.min)) {
sprintf(txBuf, "%s", preamble);
if (fnInx == 1) {
txBuf[7] = 'O';
txBuf[8] = ',';
txBuf[9] = cmdVal;
txBuf[10] = 0;
}
else {
txBuf[7] = cmdVal;
txBuf[8] = 0;
}
digitalWrite(PULSE_PORT, HIGH);
delay(20);
digitalWrite(PULSE_PORT, LOW);
delay(10);
Serial.println(txBuf);
delay(330);
}
}
}
myFile.close();
delay(60);
}
int adjustRTC(int place, int inVal) { //place is 0 for hour, 1 for minute
if (digitalRead(UP_BUTTON) == 0) {
if (inVal >= adjVals[place].upLimit) {
inVal = adjVals[place].lowLimit;
}
else {
inVal++;
}
changed = true;
}
if (digitalRead(DOWN_BUTTON) == 0) {
if (inVal <= adjVals[place].lowLimit) {
inVal = adjVals[place].upLimit;
}
else {
inVal--;
}
changed = true;
}
mtrx.setCursor(0, 0);
if (place == 0) {
mtrx.print("Hour");
}
if (place == 1) {
mtrx.print("Min ");
}
mtrx.setCursor(29, 0);
mtrx.print(inVal);
mtrx.dot(loopCount, 7); //Visual indication of time to take more action, or store changes
mtrx.update();
while ((digitalRead(UP_BUTTON) == 0) || (digitalRead(DOWN_BUTTON) == 0)) {
delay(1);
}
return inVal;
}
int getDistance(void) { //Returns distance in centimeters or 50 on timeout
int retVal = 50;
int16_t count;
count = pulseIn(LIDAR_PORT, HIGH, LIDAR_TIMEOUT);
if (count > 0) {
retVal = 3 * (count - 1000) / 40;
}
return retVal;
}
int getBright(void) {
int bv = 0;
int pcVal = analogRead(PC_PORT);
do {
} while (pcVal > threshold[bv++]);
return --bv;
}
void dispTime(void) {
char amPm = 'A';
uint8_t hrShort = t.hour;
if (t.hour > 12) {
hrShort -= 12;
amPm = 'P';
}
if (hrShort < 10) {
mtrx.print(' ');
}
mtrx.print(hrShort);
mtrx.setCursor(11, 0);
mtrx.print(':');
mtrx.setCursor(15, 0);
mtrx.print(t.min);
mtrx.print(" ");
mtrx.setCursor(29, 0);
mtrx.print(amPm);
mtrx.print('M');
}
void dispDay(void) {
mtrx.print(dow[(t.wday - 1) * 3]);
mtrx.print(dow[((t.wday - 1) * 3) + 1]);
mtrx.print(dow[((t.wday - 1) * 3) + 2]);
mtrx.setCursor(23, 0);
mtrx.print(mchar[(t.mon - 1) * 3]);
mtrx.print(mchar[((t.mon - 1) * 3) + 1]);
mtrx.print(mchar[((t.mon - 1) * 3) + 2]);
}
void dispMY(void) {
mtrx.print(t.mday);
mtrx.setCursor(17, 0);
mtrx.print(t.year);
}
void dispSDmsg(int fnInx, int viewTime) {
File myFile;
char item = '~';
int cnt = 0;
char lineBuf[8];
if (sdInit) {
myFile = SD.open(filenames[fnInx]);
mtrx.clear();
}
else {
sdInit = SD.begin(SS);
Serial.print(fnInx);
Serial.println(" failed");
mtrx.println("failed");
mtrx.update();
delay(1000);
}
if (myFile) {
while ((myFile.available()) && (item > 3)) {
for (cnt = 0; cnt < 7; cnt++) {
lineBuf[cnt] = ' ';
}
cnt = 0;
mtrx.setCursor(0, 0);
do {
item = myFile.read();
if ((item >= ' ') && (cnt < 8)) {
lineBuf[cnt++] = item;
}
} while ((item != '\n') && (item > 3));
if (cnt > 0) {
mtrx.print(lineBuf);
}
mtrx.update();
delay(viewTime);
}
myFile.close();
}
}
void setup() {
Serial.begin(2400);
delay(100);
pinMode(UP_BUTTON, INPUT_PULLUP);
pinMode(DOWN_BUTTON, INPUT_PULLUP);
pinMode(10, OUTPUT); //Ethernet CS, disable it:cannot use
pinMode(PULSE_PORT, OUTPUT);
digitalWrite(PULSE_PORT, LOW);
digitalWrite(10, HIGH);
pinMode(SS, OUTPUT); // Wired CS pin for SD
digitalWrite(SS, HIGH);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}
mtrx.begin();
mtrx.setRotation(3); //The modules come with pins horizontal,canot be butted that way
mtrx.setBright(3); //Scale of 0 to 5. Current varies 170 - 260 MA
Wire.begin(); //start i2c (required for connection)
DS3231_init(DS3231_INTCN); //register the ds3231 (DS3231_INTCN is the default address of ds3231
//, this is set by macro for no performance loss)
DS3231_get(&t);
if ((t.year < 2024) || (t.year > 2033)) { //RTC initial startup
//sec,min,hour,mday,mon,year,wday
ts s = {0, 14, 19, 7, 1, 2024, 1};
DS3231_set(s);
}
// Secure Digital Drive
sdInit = SD.begin(SS);
delay(100);
sysMode = 0;
dispSDmsg(2, 400);
}
Because of the small display character size, multiple screens (sysMode) are needed.
There being no room on the front for a manual control, a LIDAR IR distance-measuring module selects the screens,
Because the operators hands must be close when adjusting the RTC, timing was also used.
void loop() {
static int subCount = 0;
int brightInx;
static int adjVal;
int distance = getDistance(); //in centimeters
subCount++;
if (subCount >= VIEW_TIME) {
subCount = 0;
if (loopCount > 0) {
loopCount --;
}
}
DS3231_get(&t);
if (t.sec == 5) {
transmitDT();
delay(960);
}
if (t.sec == 17) {
checkSD(1);
}
if (t.sec == 10) {
checkSD(0);
}
brightInx = getBright();
mtrx.setBright(brightInx);
mtrx.clear();
mtrx.setCursor(0, 0);
switch (sysMode) {
case 0: //Hours,minutes
dispTime();
if (distance <= zones[1]) {
sysMode = 1;
}
break;
case 1: //Day of week, month both 3-character abbreviations
dispDay();
if (distance <= zones[2]) {
sysMode = 2;
}
if (distance >= zones[0]) {
sysMode = 0;
}
break;
case 2: //Date in month and full year
dispMY();
if (distance <= zones[3]) {
sysMode = 3;
dispSDmsg(3, 400); // Information message
mtrx.clear();
loopCount = 39;
}
if (distance >= zones[1]) {
sysMode = 1;
}
break;
case 3:
mtrx.print("Press ");
for (int i = 31; i < 39; i += 2) {
mtrx.dot(i, 5);
}
mtrx.dot(loopCount, 7);
mtrx.update();
if (digitalRead(UP_BUTTON) == 0) {
sysMode = 4;
adjVal = t.hour;
changed = false;
loopCount = 39;
}
if (digitalRead(DOWN_BUTTON) == 0) {
sysMode = 5;
adjVal = t.min;
changed = false;
loopCount = 39;
}
while ((digitalRead(UP_BUTTON) == 0) || (digitalRead(DOWN_BUTTON) == 0)) {
delay(100);
}
if (loopCount == 0) {
mtrx.clear();
loopCount = 42;
sysMode = 10;
}
break;
case 4: //Hour adjustment, no carry in or out
adjVal = adjustRTC(0, adjVal);
delay(200);
if (loopCount == 0) {
if (changed) {
t.min = adjVal;
DS3231_set(t);
dispSDmsg(4, 400);
}
sysMode = 10;
loopCount = 42;
}
break;
case 5: //Minute adjustment, no carry in or out
adjVal = adjustRTC(1, adjVal);
delay(200);
if (loopCount == 0) {
if (changed) {
t.min = adjVal;
DS3231_set(t);
dispSDmsg(4, 400);
}
sysMode = 10;
loopCount = 42;
}
break;
case 10:
default:
if (distance > zones[1]) {
sysMode = 0;
}
if ((!changed) && (loopCount == 40)) {
dispSDmsg(5, 200);
}
if (loopCount == 0) {
loopCount = 39; ;
mtrx.clear();
}
if (loopCount < 40) {
mtrx.dot(loopCount, 7);
mtrx.update();
}
break;
}
mtrx.update();
delay(80);
}