#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include <SPI.h>
#define USE_UI_CONTROL 0
#if USE_UI_CONTROL
#include <MD_UISwitch.h>
#endif
#define DEBUG 0
#if DEBUG
#define PRINT(s, x) { Serial.print(F(s)); Serial.print(x); }
#define PRINTS(x) Serial.print(F(x))
#define PRINTX(x) Serial.println(x, HEX)
#else
#define PRINT(s, x)
#define PRINTS(x)
#define PRINTX(x)
#endif
#define HARDWARE_TYPE MD_MAX72XX::PAROLA_HW
#define MAX_DEVICES 5
#define CLK_PIN 52
#define DATA_PIN 51
#define CS_PIN 10
MD_Parola P = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
#if USE_UI_CONTROL
const uint8_t SPEED_IN = A5;
const uint8_t DIRECTION_SET = 8;
const uint8_t INVERT_SET = 9;
const uint8_t SPEED_DEADBAND = 5;
#endif
uint8_t scrollSpeed = 25;
textEffect_t scrollEffect = PA_SCROLL_LEFT;
textPosition_t scrollAlign = PA_LEFT;
uint16_t scrollPause = 2000;
#define BUF_SIZE 75
char curMessage[BUF_SIZE] = { "" };
char newMessage[BUF_SIZE] = { "Changshu Institute of Technology" };
bool newMessageAvailable = true;
uint8_t frame = 0;
unsigned long lastAnimationTime = 0;
const unsigned long animationInterval = 200;
void drawPacman(uint8_t x, uint8_t y, uint8_t mouthPos) {
P.displayClear();
MD_MAX72XX *mx = P.getGraphicObject();
uint8_t w = mx->getColumnCount();
uint8_t h = 8; // Fixed height for LED matrix (8 rows)
if (x >= w) x = w - 1;
for (uint8_t r = 0; r < h; r++) {
for (uint8_t c = 0; c < w; c++) {
int8_t dx = c - x;
int8_t dy = r - y;
float dist = sqrt(dx*dx + dy*dy);
if (dist < 1.8) {
if (dx == 0 || (dx > 0 && dy >= -mouthPos && dy <= mouthPos) || (dx < 0 && (dy <= -mouthPos || dy >= mouthPos))) {
mx->setPoint(r, c, false);
} else {
mx->setPoint(r, c, true);
}
}
}
}
}
void drawGhost(uint8_t x, uint8_t y) {
P.displayClear();
MD_MAX72XX *mx = P.getGraphicObject();
uint8_t w = mx->getColumnCount();
uint8_t h = 8; // Fixed height for LED matrix (8 rows)
if (x >= w) x = w - 1;
for (uint8_t r = 0; r < h; r++) {
for (uint8_t c = 0; c < w; c++) {
int8_t dx = c - x;
int8_t dy = r - y;
if (abs(dx) < 2 && dy >= -1 && dy < 2) {
if (dy == 1 && (dx == -1 || dx == 1)) continue;
mx->setPoint(r, c, true);
}
}
}
if (x > 0) mx->setPoint(y-1, x-1, false);
if (x < w-1) mx->setPoint(y-1, x+1, false);
}
void animatePacmanChase() {
static int8_t pacPos = 0;
static int8_t ghostPos = 10;
static int8_t pacDir = 1;
static uint8_t mouthPos = 0;
static bool mouthOpen = true;
unsigned long currentTime = millis();
if (currentTime - lastAnimationTime < animationInterval) return;
lastAnimationTime = currentTime;
P.displayClear();
MD_MAX72XX *mx = P.getGraphicObject();
uint8_t w = mx->getColumnCount();
pacPos += pacDir;
if (pacPos <= 0 || pacPos >= w - 1) pacDir = -pacDir;
ghostPos--;
if (ghostPos < 0) ghostPos = w - 1;
if (mouthOpen) {
mouthPos++;
if (mouthPos > 2) mouthOpen = false;
} else {
mouthPos--;
if (mouthPos == 0) mouthOpen = true;
}
drawPacman(pacPos, 1, mouthPos);
drawGhost(ghostPos, 1);
}
void displayRandomAnimation() {
static uint8_t animType = 0;
static unsigned long lastChange = 0;
if (millis() - lastChange > 5000) {
animType = random(3);
lastChange = millis();
}
switch(animType) {
case 0:
animatePacmanChase();
break;
case 1:
P.displayClear();
P.displayScroll(" WOW! ", PA_CENTER, PA_PRINT, scrollSpeed);
while (P.displayAnimate()) {}
break;
case 2:
P.displayClear();
for (uint8_t i = 0; i < 5; i++) {
P.setIntensity(i * 2);
delay(200);
}
for (uint8_t i = 5; i > 0; i--) {
P.setIntensity(i * 2);
delay(200);
}
break;
}
}
#if USE_UI_CONTROL
void doUI(void) {
{
int16_t speed = map(analogRead(SPEED_IN), 0, 1023, 10, 150);
if ((speed >= ((int16_t)P.getSpeed() + SPEED_DEADBAND)) ||
(speed <= ((int16_t)P.getSpeed() - SPEED_DEADBAND))) {
P.setSpeed(speed);
scrollSpeed = speed;
}
}
if (uiDirection.read() == MD_UISwitch::KEY_PRESS) {
scrollEffect = (scrollEffect == PA_SCROLL_LEFT ? PA_SCROLL_RIGHT : PA_SCROLL_LEFT);
P.setTextEffect(scrollEffect, scrollEffect);
P.displayClear();
P.displayReset();
}
if (uiInvert.read() == MD_UISwitch::KEY_PRESS) {
P.setInvert(!P.getInvert());
}
}
#endif
void readSerial(void) {
static char *cp = newMessage;
while (Serial.available()) {
*cp = (char)Serial.read();
if ((*cp == '\n') || (cp - newMessage >= BUF_SIZE - 2)) {
*cp = '\0';
cp = newMessage;
newMessageAvailable = true;
} else {
cp++;
}
}
}
void setup() {
Serial.begin(57600);
Serial.print("\n[Changshu Institute of Technology]\nType a message for the scrolling display\nEnd message line with a newline");
#if USE_UI_CONTROL
uiDirection.begin();
uiInvert.begin();
pinMode(SPEED_IN, INPUT);
doUI();
#endif
P.begin();
P.setIntensity(2);
P.displayText(curMessage, scrollAlign, scrollSpeed, scrollPause, scrollEffect, scrollEffect);
}
void loop() {
#if USE_UI_CONTROL
doUI();
#endif
if (random(100) < 5) {
displayRandomAnimation();
} else {
if (P.displayAnimate()) {
if (newMessageAvailable) {
strcpy(curMessage, newMessage);
newMessageAvailable = false;
P.displayReset();
}
}
}
readSerial();
}