#include <MD_MAX72xx.h>
#include <SPI.h>
#include <TimeLib.h>
#include <DS1307RTC.h>
#include "Font_Data.h"
// Turn on debug statements to the serial output
#define DEBUG 1
#if DEBUG
#define PRINT(s, x) { Serial.print(F(s)); Serial.print(x); }
#define PRINTS(x) Serial.print(F(x))
#define PRINTD(x) Serial.println(x, DEC)
#else
#define PRINT(s, x)
#define PRINTS(x)
#define PRINTD(x)
#endif
#define HARDWARE_TYPE MD_MAX72XX::PAROLA_HW
#define MAX_DEVICES 4
#define CLK_PIN 13 // or SCK
#define DATA_PIN 11 // or MOSI
#define CS_PIN 10 // or SS
MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
#define DELAYTIME 40 // in milliseconds, this is scroll anim frame delay
#define CHAR_SPACING 1 // pixels between characters
#define CHAR_COLS 8 // should match the fixed width character columns
#define ANIMATION_FRAME_DELAY 30 // in milliseconds, pushwheel frame delay
struct digitData {
uint8_t oldValue, newValue; // ASCII value for the character
uint8_t index; // animation progression index
uint32_t timeLastFrame; // time the last frame started animating
uint8_t charCols; // number of valid cols in the charMap
uint8_t charMap[CHAR_COLS]; // character font bitmap
};
const uint8_t ST_INIT = 0, ST_WAIT = 1, ST_ANIM = 2;
static uint8_t state = ST_INIT;
uint8_t count = 0;
uint8_t curCol = 0;
uint8_t prevSecond = 61;
uint8_t today = 0;
uint8_t hourNow, minuteNow, secondNow;
uint8_t dstOn, dstOff;
bool isDst;
bool checkDst = false;
bool scroll = false;
bool clock = true;
bool inTemp = true;
uint32_t startTime;
uint32_t scrTime;
bool f = true;
char dateStr [30];
char ordInd[4][3] = { "th", "st", "nd", "rd" };
void updateDisplay(uint16_t numDigits, struct digitData *d)
// do the necessary to display current bitmap buffer to the LED display
{
curCol = 1;
mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::OFF);
//mx.clear();
for (int8_t i = numDigits - 1; i >= 0; i--) {
if (i == 5 || i == 2) curCol += 2;//Skip the colons
else {
for (int8_t j = d[i].charCols - 1; j >= 0; j--) {
mx.setColumn(curCol++, d[i].charMap[j]);
}
curCol += CHAR_SPACING;
}
}
mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::ON);
}
boolean displayTime() {
const uint8_t DIGITS_SIZE = 8; //"HH:MM:SS"
static struct digitData digit[DIGITS_SIZE];
// finite state machine to control what we do
switch (state) {
case ST_INIT: // Initialize the display - done once only on first call
digit[7].oldValue = 20 + secondNow % 10;
digit[6].oldValue = 20 + secondNow / 10;
digit[5].oldValue = '!';//Borrowed '!' for single column space
digit[4].oldValue = 10 + minuteNow % 10;
digit[3].oldValue = 10 + minuteNow / 10;
digit[2].oldValue = '!';
digit[1].oldValue = 10 + hourNow % 10;
digit[0].oldValue = 10 + hourNow / 10;
// Display the starting number
for (int8_t i = DIGITS_SIZE - 1; i >= 0; i--) {
digit[i].charCols = mx.getChar(digit[i].oldValue, CHAR_COLS, digit[i].charMap);
}
updateDisplay(DIGITS_SIZE, digit);
// Now we wait for a change
state = ST_WAIT;
break;
case ST_WAIT: // Check for the values displayed for changes in them
digit[7].newValue = 20 + secondNow % 10;
digit[6].newValue = 20 + secondNow / 10;
digit[5].newValue = '!';
digit[4].newValue = 10 + minuteNow % 10;
digit[3].newValue = 10 + minuteNow / 10;
digit[2].newValue = '!';
digit[1].newValue = 10 + hourNow % 10;
digit[0].newValue = 10 + hourNow / 10;
for (uint8_t i = 0; i < 8; i++) {
if (digit[i].newValue != digit[i].oldValue) {
state = ST_ANIM;
digit[i].index = 0;
digit[i].timeLastFrame = 0;
}
}
if (state == ST_WAIT) break;
case ST_ANIM:
for (uint8_t i = 0; i < 8; i++) {
//update direction from hours to seconds
if ((digit[i].newValue != digit[i].oldValue) && (millis() - digit[i].timeLastFrame >= ANIMATION_FRAME_DELAY)) {
//if mismatch is detected then start the timer for the animation
uint8_t newChar[CHAR_COLS] = { 0 };
mx.getChar(digit[i].newValue, CHAR_COLS, newChar);
//Scroll down
for (uint8_t j = 0; j < digit[i].charCols; j++) {
newChar[j] = newChar[j] >> (COL_SIZE - 1 - digit[i].index);
digit[i].charMap[j] = digit[i].charMap[j] << 1;
digit[i].charMap[j] |= newChar[j];
}
/*
// scroll up
for (uint8_t j = 0; j < digit[i].charCols; j++) {
newChar[j] = newChar[j] << (COL_SIZE - 1 - digit[i].index);
digit[i].charMap[j] = digit[i].charMap[j] >> 1;
digit[i].charMap[j] |= newChar[j];
}
*/
// set new parameters for next animation and check if we are done
digit[i].index++;
digit[i].timeLastFrame = millis();
if (digit[i].index >= COL_SIZE)
digit[i].oldValue = digit[i].newValue; // done animating
}
}
updateDisplay(DIGITS_SIZE, digit); // show new display
// are we done animating?
{
boolean allDone = true;
for (uint8_t i = 0; allDone && (i < DIGITS_SIZE); i++) {
allDone = allDone && (digit[i].oldValue == digit[i].newValue);
}
if (allDone) state = ST_WAIT;
}
break;
default:
state = 0;
}
return (state == ST_WAIT); // animation has ended
}
void scrollText(const char *p)
{
uint8_t charWidth;
uint8_t cBuf[8]; // this should be ok for all built-in fonts
uint8_t tempWidth = 0;
//PRINTS("Scrolling text\n");
//mx.clear();
while (*p != '\0')
{
charWidth = mx.getChar(*p++, sizeof(cBuf) / sizeof(cBuf[0]), cBuf);
tempWidth += charWidth + 1;
for (uint8_t i = 0; i <= charWidth; i++) // allow space between characters
{
mx.transform(MD_MAX72XX::TSL);
mx.setColumn(0, (i < charWidth) ? cBuf[i] : 0);
delay(DELAYTIME);
}
}
if (*p == 0) { // On the fly col padding for variable temp width display (justify center)
uint8_t pad;
if (inTemp) {
pad = tempWidth - 43; //Column width of " Temp In "
inTemp = !inTemp;
}
else pad = tempWidth - 21; //Column width of "Out "
if (pad < 31) pad = (31 - pad) / 2;
else pad = 0;
//Serial.print(tempWidth);
//Serial.println(" Columns");
//Serial.print("Padding ");
//Serial.print(pad);
//Serial.println(" Cols");
tempWidth = 0;
if (pad) {
for (uint8_t i = 0; i < pad; i++)
{
mx.transform(MD_MAX72XX::TSL);
mx.setColumn(0, 0);
delay(DELAYTIME);
}
}
}
}
void scrollUp () {
for (uint8_t i = 0; i < 8; i ++) {
mx.transform(MD_MAX72XX::TSU);
delay(DELAYTIME);
}
}
void doDateStr() {
today = day();
if (month() == 3 && day() == dstOn || month() == 10 && day() == dstOff) checkDst = 1;
uint8_t oi = ((day() + 90) % 100 - 10) % 10;
if (oi > 3) oi = 0;
char thisMonth [12];
strcpy (thisMonth, monthStr(month()));//buffer issue sprintf avr, so this line is a necessary workaround
sprintf(dateStr, "%s %d%s %s %d ", dayStr(weekday()), day(), ordInd[oi], thisMonth, year());
Serial.print("Today is: ");
Serial.println(dateStr);
}
/*//This commented code calculates the DST offset timestamps for UK GMT
time_t tmConvert_t(int YYYY, byte MM, byte DD, byte hh, byte mm, byte ss) {
tmElements_t tmSet;
tmSet.Year = YYYY - 1970;
tmSet.Month = MM;
tmSet.Day = DD;
tmSet.Hour = hh;
tmSet.Minute = mm;
tmSet.Second = ss;
return makeTime(tmSet);
}
void calDST() {
dstOn = (31 - (5 * year() / 4 + 4) % 7); //March DST start at 1 am
dstOff = (31 - (5 * year() / 4 + 1) % 7); //October DST end at 1 am GMT
time_t sdst_tm = tmConvert_t(year(), 3, dstOn, 1, 0, 0);
time_t edst_tm = tmConvert_t(year(), 10, dstOff, 1, 0, 0);
if (now() >= sdst_tm && now() < edst_tm) isDst = 1;
else isDst = 0;
isDst ? Serial.println("Time is BST") : Serial.println("Time is GMT");
}
*/
void setup()
{
#if DEBUG
Serial.begin(115200);
#endif
PRINTS("\n[MD_MAX72XX Test & Demo]\n");
mx.begin();
mx.setFont(numeric7Se);
mx.control(MD_MAX72XX::INTENSITY, 0);
//setTime(0, 59, 30, 31, 3, 2024);
setSyncProvider(RTC.get);
setSyncInterval(1800);//30 minutes. Uno is a poor timekeeper!
//calDST();
randomSeed(analogRead(A0));
}
void loop() {}