Made MD* libraries (Parola, MAX72XX) project specific, to be able to use the newest software version from github.
This commit is contained in:
@@ -0,0 +1,441 @@
|
||||
// Use the Parola library to scroll text on the display
|
||||
//
|
||||
// Demonstrates the use of the scrolling function to display text received
|
||||
// from the serial interface and shared with a menu to change the parameters
|
||||
// for the scrolling display and save to EEPROM.
|
||||
//
|
||||
// User can enter text on the serial monitor and this will display as a
|
||||
// scrolling message on the display.
|
||||
//
|
||||
// Speed, scroll direction, brightness and invert are controlled from the menu.
|
||||
//
|
||||
// Interface for menu control can be either 3 momentary on (tact) switches or
|
||||
// a rotary encoder with integrated switch.
|
||||
//
|
||||
// UISwitch library can be found at https://github.com/MajicDesigns/MD_UISwitch
|
||||
// Rotary Encoder library can be found at https://github.com/MajicDesigns/MD_REncoder
|
||||
// Menu library can be found at https://github.com/MajicDesigns/MD_Menu
|
||||
// MD_MAX72XX library can be found at https://github.com/MajicDesigns/MD_MAX72XX
|
||||
//
|
||||
|
||||
#include <EEPROM.h>
|
||||
#include <MD_Parola.h>
|
||||
#include <MD_MAX72xx.h>
|
||||
#include <SPI.h>
|
||||
#include <MD_Menu.h>
|
||||
|
||||
// set to 1 if depending on the menu control interface
|
||||
#define MENU_SWITCH 0 // tact switches
|
||||
#define MENU_RENCODER 1 // rotary encoder
|
||||
|
||||
const uint16_t MENU_TIMEOUT = 5000; // in milliseconds
|
||||
const uint16_t EEPROM_ADDRESS = 0; // config data address
|
||||
|
||||
// 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 PRINTX(x) Serial.println(x, HEX)
|
||||
#else
|
||||
#define PRINT(s, x)
|
||||
#define PRINTS(x)
|
||||
#define PRINTX(x)
|
||||
#endif
|
||||
|
||||
// Define the number of devices we have in the chain and the hardware interface
|
||||
// NOTE: These pin numbers will probably not work with your hardware and may
|
||||
// need to be adapted
|
||||
#define HARDWARE_TYPE MD_MAX72XX::PAROLA_HW
|
||||
#define MAX_DEVICES 8
|
||||
#define CLK_PIN 13
|
||||
#define DATA_PIN 11
|
||||
#define CS_PIN 10
|
||||
|
||||
// HARDWARE SPI
|
||||
MD_Parola P = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
|
||||
// SOFTWARE SPI
|
||||
//MD_Parola P = MD_Parola(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES);
|
||||
|
||||
// Scrolling parameters configuration block
|
||||
struct cfgParameter_t
|
||||
{
|
||||
uint8_t signature[2]; // signature for EEPROM
|
||||
|
||||
uint8_t speed; // animation frame delay
|
||||
uint16_t pause = 2000;// animation pause in milliseconds
|
||||
uint8_t bright; // display intensity
|
||||
textEffect_t effect; // text animation effect
|
||||
textPosition_t align; // text alignment at pause
|
||||
bool invert = false; // inverted display
|
||||
} Config;
|
||||
|
||||
// Global message buffers shared by Serial and Scrolling functions
|
||||
#define BUF_SIZE 75
|
||||
char curMessage[BUF_SIZE] = { "" };
|
||||
char newMessage[BUF_SIZE] = { "Hello! Enter new message?" };
|
||||
bool newMessageAvailable = true;
|
||||
|
||||
// Function prototypes for Navigation/Display
|
||||
bool display(MD_Menu::userDisplayAction_t, char*);
|
||||
MD_Menu::userNavAction_t navigation(uint16_t &incDelta);
|
||||
void *mnuValueRqst(MD_Menu::mnuId_t id, bool bGet);
|
||||
|
||||
// Menu definition
|
||||
const PROGMEM MD_Menu::mnuHeader_t mnuHdr[] =
|
||||
{
|
||||
{ 10, "Menu", 10, 15, 0 },
|
||||
};
|
||||
|
||||
const PROGMEM MD_Menu::mnuItem_t mnuItm[] =
|
||||
{
|
||||
// Starting (Root) menu
|
||||
{ 10, "Speed", MD_Menu::MNU_INPUT, 10 },
|
||||
{ 11, "Pause", MD_Menu::MNU_INPUT, 11 },
|
||||
{ 12, "Scroll", MD_Menu::MNU_INPUT, 12 },
|
||||
{ 13, "Align", MD_Menu::MNU_INPUT, 13 },
|
||||
{ 14, "Bright", MD_Menu::MNU_INPUT, 14 },
|
||||
{ 15, "Invert", MD_Menu::MNU_INPUT, 15 },
|
||||
};
|
||||
|
||||
const PROGMEM char listAlign[] = "L|C|R";
|
||||
const PROGMEM char listScroll[] = "L|R";
|
||||
|
||||
const PROGMEM MD_Menu::mnuInput_t mnuInp[] =
|
||||
{
|
||||
{ 10, "Spd", MD_Menu::INP_INT, mnuValueRqst, 3, 0, 0, 255, 0, 10, nullptr },
|
||||
{ 11, "Pse", MD_Menu::INP_INT, mnuValueRqst, 4, 0, 0, 2000, 0, 10, nullptr },
|
||||
{ 12, "Scrl", MD_Menu::INP_LIST, mnuValueRqst, 1, 0, 0, 0, 0, 0, listScroll },
|
||||
{ 13, "Algn", MD_Menu::INP_LIST, mnuValueRqst, 1, 0, 0, 0, 0, 0, listAlign },
|
||||
{ 14, "Bri", MD_Menu::INP_INT, mnuValueRqst, 2, 0, 0, 15, 0, 10, nullptr },
|
||||
{ 15, "Inv", MD_Menu::INP_BOOL, mnuValueRqst, 1, 0, 0, 0, 0, 0, nullptr },
|
||||
};
|
||||
|
||||
// Menu global object
|
||||
MD_Menu M(navigation, display,// user navigation and display
|
||||
mnuHdr, ARRAY_SIZE(mnuHdr), // menu header data
|
||||
mnuItm, ARRAY_SIZE(mnuItm), // menu item data
|
||||
mnuInp, ARRAY_SIZE(mnuInp));// menu input data
|
||||
|
||||
// Configuration Load/Save to/from EEPROM
|
||||
void paramSave(void)
|
||||
{
|
||||
PRINTS("\nSaving Parameters");
|
||||
EEPROM.put(EEPROM_ADDRESS, Config);
|
||||
}
|
||||
|
||||
void paramLoad(void)
|
||||
{
|
||||
uint8_t sig[2] = { 0xa5, 0x5a };
|
||||
|
||||
PRINTS("\nLoading Parameters");
|
||||
|
||||
EEPROM.get(EEPROM_ADDRESS, Config);
|
||||
|
||||
if (Config.signature[0] != sig[0] ||
|
||||
Config.signature[1] != sig[1])
|
||||
{
|
||||
PRINTS("\n\nSetting Default Parameters");
|
||||
Config.signature[0] = sig[0];
|
||||
Config.signature[1] = sig[1];
|
||||
Config.speed = 25;
|
||||
Config.bright = 7;
|
||||
Config.effect = PA_SCROLL_LEFT;
|
||||
Config.align = PA_LEFT;
|
||||
Config.pause = 2000;
|
||||
Config.invert = false;
|
||||
|
||||
paramSave();
|
||||
}
|
||||
}
|
||||
|
||||
// Menu system callback functions
|
||||
void *mnuValueRqst(MD_Menu::mnuId_t id, bool bGet)
|
||||
// Value request callback for variables
|
||||
{
|
||||
static MD_Menu::value_t v;
|
||||
|
||||
switch (id)
|
||||
{
|
||||
case 10: // Speed
|
||||
if (bGet)
|
||||
v.value = Config.speed;
|
||||
else
|
||||
{
|
||||
Config.speed = v.value;
|
||||
P.setSpeed(Config.speed);
|
||||
PRINT("\nSet speed: ", Config.speed);
|
||||
}
|
||||
break;
|
||||
|
||||
case 11: // Pause
|
||||
if (bGet)
|
||||
v.value = Config.pause;
|
||||
else
|
||||
{
|
||||
Config.pause = v.value;
|
||||
P.setPause(Config.pause);
|
||||
PRINT("\nSet pause: ", Config.pause);
|
||||
}
|
||||
break;
|
||||
|
||||
case 12: // Scroll
|
||||
if (bGet)
|
||||
v.value = (Config.effect == PA_SCROLL_LEFT ? 0 : 1);
|
||||
else
|
||||
{
|
||||
Config.effect = (v.value == 0 ? PA_SCROLL_LEFT : PA_SCROLL_RIGHT);
|
||||
P.setTextEffect(Config.effect, Config.effect);
|
||||
PRINT("\nSet scroll effect: ", Config.effect);
|
||||
}
|
||||
break;
|
||||
|
||||
case 13: // Align
|
||||
if (bGet)
|
||||
{
|
||||
switch (Config.align)
|
||||
{
|
||||
case PA_LEFT: v.value = 0; break;
|
||||
case PA_CENTER: v.value = 1; break;
|
||||
case PA_RIGHT: v.value = 2; break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (v.value)
|
||||
{
|
||||
case 0: Config.align = PA_LEFT; break;
|
||||
case 1: Config.align = PA_CENTER; break;
|
||||
case 2: Config.align = PA_RIGHT; break;
|
||||
}
|
||||
P.setTextAlignment(Config.align);
|
||||
PRINT("\nSet align: ", Config.align);
|
||||
}
|
||||
break;
|
||||
|
||||
case 14: // Bright
|
||||
if (bGet)
|
||||
v.value = Config.bright;
|
||||
else
|
||||
{
|
||||
Config.bright = v.value;
|
||||
P.setIntensity(Config.bright);
|
||||
PRINT("\nSet intensity: ", Config.bright);
|
||||
}
|
||||
break;
|
||||
|
||||
case 15: // Invert
|
||||
if (bGet)
|
||||
v.value = Config.invert;
|
||||
else
|
||||
{
|
||||
Config.invert = v.value;
|
||||
P.setInvert(Config.invert);
|
||||
PRINT("\nSet invert: ", Config.invert);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// if things were requested, return the buffer
|
||||
if (bGet)
|
||||
return(&v);
|
||||
else // save the parameters
|
||||
paramSave();
|
||||
|
||||
return(nullptr);
|
||||
}
|
||||
|
||||
bool display(MD_Menu::userDisplayAction_t action, char *msg)
|
||||
// Simple output to a one line LED Matrix display managed
|
||||
// by the Parola library.
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
case MD_Menu::DISP_CLEAR:
|
||||
P.displayClear();
|
||||
break;
|
||||
|
||||
case MD_Menu::DISP_L0:
|
||||
// P.print(msg); // only one zone, no line 0
|
||||
break;
|
||||
|
||||
case MD_Menu::DISP_L1:
|
||||
P.print(msg);
|
||||
break;
|
||||
}
|
||||
|
||||
return(true);
|
||||
}
|
||||
|
||||
#if MENU_SWITCH
|
||||
// Implemented as momentary on 3 switches
|
||||
// one switch each for INC, DEC
|
||||
// one switch for SEL (click) or ESC (long press)
|
||||
|
||||
#include <MD_UISwitch.h>
|
||||
|
||||
const uint8_t INC_PIN = 9;
|
||||
const uint8_t DEC_PIN = 8;
|
||||
const uint8_t CTL_PIN = 6;
|
||||
|
||||
uint8_t pins[] = { INC_PIN, DEC_PIN, CTL_PIN };
|
||||
|
||||
MD_UISwitch_Digital swNav(pins, ARRAY_SIZE(pins));
|
||||
|
||||
void setupNav(void)
|
||||
{
|
||||
swNav.begin();
|
||||
swNav.enableRepeat(false);
|
||||
}
|
||||
|
||||
MD_Menu::userNavAction_t navigation(uint16_t &incDelta)
|
||||
{
|
||||
MD_Menu::userNavAction_t nav = MD_Menu::NAV_NULL;
|
||||
|
||||
switch (swNav.read())
|
||||
{
|
||||
case MD_UISwitch::KEY_PRESS:
|
||||
{
|
||||
switch (swNav.getKey())
|
||||
{
|
||||
case INC_PIN: nav = MD_Menu::NAV_INC; break;
|
||||
case DEC_PIN: nav = MD_Menu::NAV_DEC; break;
|
||||
case ESC_PIN: nav = MD_Menu::NAV_SEL; break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case MD_UISwitch::KEY_LONGPRESS:
|
||||
{
|
||||
if (swNav.getKey() == 2)
|
||||
nav = MD_Menu::NAV_ESC;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return(nav);
|
||||
}
|
||||
#endif // USE_UI_CONTROL
|
||||
|
||||
#if MENU_RENCODER
|
||||
// Implemented as a rotary encoder with integrated push switch.
|
||||
// Rotate encoder clockwise for INC, anti-clockwise for DEC
|
||||
// Switch for SEL (press) or ESC (long press)
|
||||
//
|
||||
// If a value is being edited, the rotary encoder will also apply
|
||||
// a factor for how fast the RE is being turned to increase the rate
|
||||
// of change of values. This will be applied by the menu library to
|
||||
// numeric input only.
|
||||
|
||||
#include <MD_REncoder.h>
|
||||
#include <MD_UISwitch.h>
|
||||
|
||||
extern MD_Menu M;
|
||||
|
||||
const uint8_t RE_A_PIN = 2;
|
||||
const uint8_t RE_B_PIN = 3;
|
||||
const uint8_t CTL_PIN = 4;
|
||||
|
||||
MD_REncoder RE(RE_A_PIN, RE_B_PIN);
|
||||
MD_UISwitch_Digital swCtl(CTL_PIN);
|
||||
|
||||
void setupNav(void)
|
||||
{
|
||||
RE.begin();
|
||||
swCtl.begin();
|
||||
swCtl.enableRepeat(false);
|
||||
}
|
||||
|
||||
MD_Menu::userNavAction_t navigation(uint16_t &incDelta)
|
||||
{
|
||||
uint8_t re = RE.read();
|
||||
|
||||
if (re != DIR_NONE)
|
||||
{
|
||||
if (M.isInEdit()) incDelta = 1 << abs(RE.speed() >> 3);
|
||||
return(re == DIR_CCW ? MD_Menu::NAV_DEC : MD_Menu::NAV_INC);
|
||||
}
|
||||
|
||||
switch (swCtl.read())
|
||||
{
|
||||
case MD_UISwitch::KEY_PRESS: return(MD_Menu::NAV_SEL);
|
||||
case MD_UISwitch::KEY_LONGPRESS: return(MD_Menu::NAV_ESC);
|
||||
}
|
||||
|
||||
return(MD_Menu::NAV_NULL);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Serial input for new message
|
||||
void readSerial(void)
|
||||
{
|
||||
static char *cp = newMessage;
|
||||
|
||||
while (Serial.available())
|
||||
{
|
||||
*cp = (char)Serial.read();
|
||||
if ((*cp == '\n') || (cp - newMessage >= BUF_SIZE-2)) // end of message character or full buffer
|
||||
{
|
||||
*cp = '\0'; // end the string
|
||||
// restart the index for next filling spree and flag we have a message waiting
|
||||
cp = newMessage;
|
||||
newMessageAvailable = true;
|
||||
}
|
||||
else // move char pointer to next position
|
||||
cp++;
|
||||
}
|
||||
}
|
||||
|
||||
void setup()
|
||||
{
|
||||
Serial.begin(57600);
|
||||
Serial.print("\n[Parola Scrolling Display]\nPress SEL button for configuration menu");
|
||||
Serial.print("\nType a message for the scrolling display\nEnd message line with a newline");
|
||||
|
||||
paramLoad();
|
||||
|
||||
setupNav();
|
||||
|
||||
M.begin();
|
||||
M.setAutoStart(true);
|
||||
M.setTimeout(MENU_TIMEOUT);
|
||||
|
||||
P.begin();
|
||||
P.setInvert(Config.invert);
|
||||
P.setIntensity(Config.bright);
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
static bool wasInMenu = true; // ensure we initialise the display first
|
||||
|
||||
if (wasInMenu && !M.isInMenu()) // was running, but not any more
|
||||
{
|
||||
// Reset the display to show message
|
||||
PRINTS("\nMenu Stopped, running message");
|
||||
P.displayClear();
|
||||
P.displayText(curMessage, Config.align, Config.speed, Config.pause, Config.effect, Config.effect);
|
||||
wasInMenu = false;
|
||||
}
|
||||
|
||||
wasInMenu = M.isInMenu(); // save current status
|
||||
M.runMenu(); // run or autostart the menu
|
||||
|
||||
if (!M.isInMenu()) // not running the menu? do something else
|
||||
{
|
||||
// animate display and check swap message if ended
|
||||
if (P.displayAnimate())
|
||||
{
|
||||
if (newMessageAvailable)
|
||||
{
|
||||
strcpy(curMessage, newMessage);
|
||||
newMessageAvailable = false;
|
||||
}
|
||||
P.displayReset();
|
||||
}
|
||||
}
|
||||
|
||||
readSerial(); // receive new serial characters
|
||||
}
|
||||
|
Reference in New Issue
Block a user