Made MD* libraries (Parola, MAX72XX) project specific, to be able to use the newest software version from github.

This commit is contained in:
2019-02-01 15:21:58 +01:00
parent fe9f25c856
commit c7547144d2
761 changed files with 108577 additions and 2 deletions

View File

@@ -0,0 +1,390 @@
// Use the MD_MAX72XX library to scroll text on the display
// received through the ESP8266 WiFi interface.
//
// Demonstrates the use of the callback function to control what
// is scrolled on the display text. User can enter text through
// a web browser and this will display as a scrolling message on
// the display.
//
// IP address for the ESP8266 is displayed on the scrolling display
// after startup initialization and connected to the WiFi network.
//
// Connections for ESP8266 hardware SPI are:
// Vcc 3v3 LED matrices seem to work at 3.3V
// GND GND GND
// DIN D7 HSPID or HMOSI
// CS or LD D8 HSPICS or HCS
// CLK D5 CLK or HCLK
//
#include <ESP8266WiFi.h>
#include <MD_MAX72xx.h>
#include <SPI.h>
#define PRINT_CALLBACK 0
#define DEBUG 0
#define LED_HEARTBEAT 0
#if DEBUG
#define PRINT(s, v) { Serial.print(F(s)); Serial.print(v); }
#define PRINTS(s) { Serial.print(F(s)); }
#else
#define PRINT(s, v)
#define PRINTS(s)
#endif
#if LED_HEARTBEAT
#define HB_LED D2
#define HB_LED_TIME 500 // in milliseconds
#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 D5 // or SCK
#define DATA_PIN D7 // or MOSI
#define CS_PIN D8 // or SS
// SPI hardware interface
MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
// Arbitrary pins
//MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES);
// WiFi login parameters - network name and password
const char* ssid = "";
const char* password = "";
// WiFi Server object and parameters
WiFiServer server(80);
// Global message buffers shared by Wifi and Scrolling functions
const uint8_t MESG_SIZE = 255;
const uint8_t CHAR_SPACING = 1;
const uint8_t SCROLL_DELAY = 75;
char curMessage[MESG_SIZE];
char newMessage[MESG_SIZE];
bool newMessageAvailable = false;
char WebResponse[] = "HTTP/1.1 200 OK\nContent-Type: text/html\n\n";
char WebPage[] =
"<!DOCTYPE html>" \
"<html>" \
"<head>" \
"<title>MajicDesigns Test Page</title>" \
"<script>" \
"strLine = \"\";" \
"function SendText()" \
"{" \
" nocache = \"/&nocache=\" + Math.random() * 1000000;" \
" var request = new XMLHttpRequest();" \
" strLine = \"&MSG=\" + document.getElementById(\"txt_form\").Message.value;" \
" request.open(\"GET\", strLine + nocache, false);" \
" request.send(null);" \
"}" \
"</script>" \
"</head>" \
"<body>" \
"<p><b>MD_MAX72xx set message</b></p>" \
"<form id=\"txt_form\" name=\"frmText\">" \
"<label>Msg:<input type=\"text\" name=\"Message\" maxlength=\"255\"></label><br><br>" \
"</form>" \
"<br>" \
"<input type=\"submit\" value=\"Send Text\" onclick=\"SendText()\">" \
"</body>" \
"</html>";
char *err2Str(wl_status_t code)
{
switch (code)
{
case WL_IDLE_STATUS: return("IDLE"); break; // WiFi is in process of changing between statuses
case WL_NO_SSID_AVAIL: return("NO_SSID_AVAIL"); break; // case configured SSID cannot be reached
case WL_CONNECTED: return("CONNECTED"); break; // successful connection is established
case WL_CONNECT_FAILED: return("CONNECT_FAILED"); break; // password is incorrect
case WL_DISCONNECTED: return("CONNECT_FAILED"); break; // module is not configured in station mode
default: return("??");
}
}
uint8_t htoi(char c)
{
c = toupper(c);
if ((c >= '0') && (c <= '9')) return(c - '0');
if ((c >= 'A') && (c <= 'F')) return(c - 'A' + 0xa);
return(0);
}
boolean getText(char *szMesg, char *psz, uint8_t len)
{
boolean isValid = false; // text received flag
char *pStart, *pEnd; // pointer to start and end of text
// get pointer to the beginning of the text
pStart = strstr(szMesg, "/&MSG=");
if (pStart != NULL)
{
pStart += 6; // skip to start of data
pEnd = strstr(pStart, "/&");
if (pEnd != NULL)
{
while (pStart != pEnd)
{
if ((*pStart == '%') && isdigit(*(pStart+1)))
{
// replace %xx hex code with the ASCII character
char c = 0;
pStart++;
c += (htoi(*pStart++) << 4);
c += htoi(*pStart++);
*psz++ = c;
}
else
*psz++ = *pStart++;
}
*psz = '\0'; // terminate the string
isValid = true;
}
}
return(isValid);
}
void handleWiFi(void)
{
static enum { S_IDLE, S_WAIT_CONN, S_READ, S_EXTRACT, S_RESPONSE, S_DISCONN } state = S_IDLE;
static char szBuf[1024];
static uint16_t idxBuf = 0;
static WiFiClient client;
static uint32_t timeStart;
switch (state)
{
case S_IDLE: // initialize
PRINTS("\nS_IDLE");
idxBuf = 0;
state = S_WAIT_CONN;
break;
case S_WAIT_CONN: // waiting for connection
{
client = server.available();
if (!client) break;
if (!client.connected()) break;
#if DEBUG
char szTxt[20];
sprintf(szTxt, "%03d:%03d:%03d:%03d", client.remoteIP()[0], client.remoteIP()[1], client.remoteIP()[2], client.remoteIP()[3]);
PRINT("\nNew client @ ", szTxt);
#endif
timeStart = millis();
state = S_READ;
}
break;
case S_READ: // get the first line of data
PRINTS("\nS_READ");
while (client.available())
{
char c = client.read();
if ((c == '\r') || (c == '\n'))
{
szBuf[idxBuf] = '\0';
client.flush();
PRINT("\nRecv: ", szBuf);
state = S_EXTRACT;
}
else
szBuf[idxBuf++] = (char)c;
}
if (millis() - timeStart > 1000)
{
PRINTS("\nWait timeout");
state = S_DISCONN;
}
break;
case S_EXTRACT: // extract data
PRINTS("\nS_EXTRACT");
// Extract the string from the message if there is one
newMessageAvailable = getText(szBuf, newMessage, MESG_SIZE);
PRINT("\nNew Msg: ", newMessage);
state = S_RESPONSE;
break;
case S_RESPONSE: // send the response to the client
PRINTS("\nS_RESPONSE");
// Return the response to the client (web page)
client.print(WebResponse);
client.print(WebPage);
state = S_DISCONN;
break;
case S_DISCONN: // disconnect client
PRINTS("\nS_DISCONN");
client.flush();
client.stop();
state = S_IDLE;
break;
default: state = S_IDLE;
}
}
void scrollDataSink(uint8_t dev, MD_MAX72XX::transformType_t t, uint8_t col)
// Callback function for data that is being scrolled off the display
{
#if PRINT_CALLBACK
Serial.print("\n cb ");
Serial.print(dev);
Serial.print(' ');
Serial.print(t);
Serial.print(' ');
Serial.println(col);
#endif
}
uint8_t scrollDataSource(uint8_t dev, MD_MAX72XX::transformType_t t)
// Callback function for data that is required for scrolling into the display
{
static enum { S_IDLE, S_NEXT_CHAR, S_SHOW_CHAR, S_SHOW_SPACE } state = S_IDLE;
static char *p;
static uint16_t curLen, showLen;
static uint8_t cBuf[8];
uint8_t colData = 0;
// finite state machine to control what we do on the callback
switch (state)
{
case S_IDLE: // reset the message pointer and check for new message to load
PRINTS("\nS_IDLE");
p = curMessage; // reset the pointer to start of message
if (newMessageAvailable) // there is a new message waiting
{
strcpy(curMessage, newMessage); // copy it in
newMessageAvailable = false;
}
state = S_NEXT_CHAR;
break;
case S_NEXT_CHAR: // Load the next character from the font table
PRINTS("\nS_NEXT_CHAR");
if (*p == '\0')
state = S_IDLE;
else
{
showLen = mx.getChar(*p++, sizeof(cBuf) / sizeof(cBuf[0]), cBuf);
curLen = 0;
state = S_SHOW_CHAR;
}
break;
case S_SHOW_CHAR: // display the next part of the character
PRINTS("\nS_SHOW_CHAR");
colData = cBuf[curLen++];
if (curLen < showLen)
break;
// set up the inter character spacing
showLen = (*p != '\0' ? CHAR_SPACING : (MAX_DEVICES*COL_SIZE)/2);
curLen = 0;
state = S_SHOW_SPACE;
// fall through
case S_SHOW_SPACE: // display inter-character spacing (blank column)
PRINT("\nS_ICSPACE: ", curLen);
PRINT("/", showLen);
curLen++;
if (curLen == showLen)
state = S_NEXT_CHAR;
break;
default:
state = S_IDLE;
}
return(colData);
}
void scrollText(void)
{
static uint32_t prevTime = 0;
// Is it time to scroll the text?
if (millis() - prevTime >= SCROLL_DELAY)
{
mx.transform(MD_MAX72XX::TSL); // scroll along - the callback will load all the data
prevTime = millis(); // starting point for next time
}
}
void setup()
{
#if DEBUG
Serial.begin(115200);
PRINTS("\n[MD_MAX72XX WiFi Message Display]\nType a message for the scrolling display from your internet browser");
#endif
#if LED_HEARTBEAT
pinMode(HB_LED, OUTPUT);
digitalWrite(HB_LED, LOW);
#endif
// Display initialization
mx.begin();
mx.setShiftDataInCallback(scrollDataSource);
mx.setShiftDataOutCallback(scrollDataSink);
curMessage[0] = newMessage[0] = '\0';
// Connect to and initialize WiFi network
PRINT("\nConnecting to ", ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED)
{
PRINT("\n", err2Str(WiFi.status()));
delay(500);
}
PRINTS("\nWiFi connected");
// Start the server
server.begin();
PRINTS("\nServer started");
// Set up first message as the IP address
sprintf(curMessage, "%03d:%03d:%03d:%03d", WiFi.localIP()[0], WiFi.localIP()[1], WiFi.localIP()[2], WiFi.localIP()[3]);
PRINT("\nAssigned IP ", curMessage);
}
void loop()
{
#if LED_HEARTBEAT
static uint32_t timeLast = 0;
if (millis() - timeLast >= HB_LED_TIME)
{
digitalWrite(HB_LED, digitalRead(HB_LED) == LOW ? HIGH : LOW);
timeLast = millis();
}
#endif
handleWiFi();
scrollText();
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,125 @@
// Use the MD_MAX72XX library to Print some text on the display
// while cycling through the hardware types.
//
// Easy way to determine what type of hardware is being used.
// Prints out the module type - the one that is legible is the type
// of hardware being used.
//
#include <MD_MAX72xx.h>
#include <SPI.h>
// 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 11
#define CLK_PIN 13 // or SCK
#define DATA_PIN 11 // or MOSI
#define CS_PIN 10 // or SS
// SPI hardware interface
MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
// Arbitrary pins
//MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES);
// Text parameters
#define CHAR_SPACING 1 // pixels between characters
// Global message buffers shared by Serial and Scrolling functions
#define BUF_SIZE 20
char message[BUF_SIZE] = "";
bool newMessageAvailable = true;
void newMessage(void)
{
static uint8_t msgIndex = 0;
MD_MAX72XX::moduleType_t mod;
switch (msgIndex)
{
case 0: mod = MD_MAX72XX::PAROLA_HW; strcpy(message, "Parola"); break;
case 1: mod = MD_MAX72XX::GENERIC_HW; strcpy(message, "Generic"); break;
case 2: mod = MD_MAX72XX::ICSTATION_HW; strcpy(message, "ICStation"); break;
case 3: mod = MD_MAX72XX::FC16_HW; strcpy(message, "FC16"); break;
}
msgIndex = (msgIndex + 1) % 4;
mx.setModuleType(mod); // change the module type
}
void printText(uint8_t modStart, uint8_t modEnd, char *pMsg)
// Print the text string to the LED matrix modules specified.
// Message area is padded with blank columns after printing.
{
uint8_t state = 0;
uint8_t curLen;
uint16_t showLen;
uint8_t cBuf[8];
int16_t col = ((modEnd + 1) * COL_SIZE) - 1;
mx.control(modStart, modEnd, MD_MAX72XX::UPDATE, MD_MAX72XX::OFF);
mx.clear();
do // finite state machine to print the characters in the space available
{
switch(state)
{
case 0: // Load the next character from the font table
// if we reached end of message, reset the message pointer
if (*pMsg == '\0')
{
showLen = col - (modEnd * COL_SIZE); // padding characters
state = 2;
break;
}
// retrieve the next character form the font file
showLen = mx.getChar(*pMsg++, sizeof(cBuf)/sizeof(cBuf[0]), cBuf);
curLen = 0;
state++;
// !! deliberately fall through to next state to start displaying
case 1: // display the next part of the character
mx.setColumn(col--, cBuf[curLen++]);
// done with font character, now display the space between chars
if (curLen == showLen)
{
showLen = CHAR_SPACING;
state = 2;
}
break;
case 2: // initialize state for displaying empty columns
curLen = 0;
state++;
// fall through
case 3: // display inter-character spacing or end of message padding (blank columns)
mx.setColumn(col--, 0);
curLen++;
if (curLen == showLen)
state = 0;
break;
default:
col = -1; // this definitely ends the do loop
}
} while (col >= (modStart * COL_SIZE));
mx.control(modStart, modEnd, MD_MAX72XX::UPDATE, MD_MAX72XX::ON);
}
void setup()
{
mx.begin();
}
void loop()
{
newMessage();
printText(0, MAX_DEVICES-1, message);
delay(2000);
}

View File

@@ -0,0 +1,253 @@
#include "MD_EyePair.h"
// Packing and unpacking nybbles into a byte
#define PACK_RC(r, c) ((r<<4)|(c&0xf))
#define UNPACK_R(rc) (rc>>4)
#define UNPACK_C(rc) (rc&0xf)
#define SMALL_EYEBALL 0
// Class static variables
#if SMALL_EYEBALL
uint8_t MD_EyePair::_pupilData[] =
{
/* P_TL */ PACK_RC(2,5), /* P_TC */ PACK_RC(2,4), /* P_TR */ PACK_RC(2,3),
/* P_ML */ PACK_RC(3,5), /* P_MC */ PACK_RC(3,4), /* P_MR */ PACK_RC(3,3),
/* P_BL */ PACK_RC(4,5), /* P_BC */ PACK_RC(4,4), /* P_BR */ PACK_RC(4,3),
};
// Eye related information
uint8_t MD_EyePair::_eyeballData[EYEBALL_ROWS] = { 0x00, 0x3c, 0x7e, 0x7e, 0x7e, 0x7e, 0x3c, 0x00 }; // row data
#define LAST_BLINK_ROW 6 // last row for the blink animation
#else
uint8_t MD_EyePair::_pupilData[] =
{
/* P_TL */ PACK_RC(3,5), /* P_TC */ PACK_RC(3,4), /* P_TR */ PACK_RC(3,3),
/* P_ML */ PACK_RC(4,5), /* P_MC */ PACK_RC(4,4), /* P_MR */ PACK_RC(4,3),
/* P_BL */ PACK_RC(5,5), /* P_BC */ PACK_RC(5,4), /* P_BR */ PACK_RC(5,3),
};
uint8_t MD_EyePair::_eyeballData[EYEBALL_ROWS] = { 0x3c, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x3c }; // row data
#define LAST_BLINK_ROW 7 // last row for the blink animation
#endif
// Random seed creation --------------------------
// Adapted from http://www.utopiamechanicus.com/article/arduino-better-random-numbers/
uint16_t MD_EyePair::bitOut(uint8_t port)
{
static bool firstTime = true;
uint32_t prev = 0;
uint32_t bit1 = 0, bit0 = 0;
uint32_t x = 0, limit = 99;
if (firstTime)
{
firstTime = false;
prev = analogRead(port);
}
while (limit--)
{
x = analogRead(port);
bit1 = (prev != x ? 1 : 0);
prev = x;
x = analogRead(port);
bit0 = (prev != x ? 1 : 0);
prev = x;
if (bit1 != bit0)
break;
}
return(bit1);
}
uint32_t MD_EyePair::seedOut(uint16_t noOfBits, uint8_t port)
{
// return value with 'noOfBits' random bits set
uint32_t seed = 0;
for (int i = 0; i<noOfBits; ++i)
seed = (seed << 1) | bitOut(port);
return(seed);
}
//------------------------------------------------------------------------------
MD_EyePair::MD_EyePair(void)
{
_pupilCurPos = P_MC;
_timeLast = 0;
_inBlinkCycle = false;
};
void MD_EyePair::drawEyeball()
// Draw the iris on the display(s)
{
_M->control(MD_MAX72XX::UPDATE, MD_MAX72XX::OFF);
_M->clear(_sd, _ed); // clear out current display
// Display the iris row data from the data array
for (uint8_t i=0; i<EYEBALL_ROWS; i++)
_M->setRow(_sd, _ed, i, _eyeballData[i]);
_M->control(MD_MAX72XX::UPDATE, MD_MAX72XX::ON);
}
bool MD_EyePair::blinkEyeball(bool bFirst)
// Blink the iris. If this is the first call in the cycle, bFirst will be set true.
// Return true if the blink is still active, false otherwise.
{
if (bFirst)
{
_lastBlinkTime = millis();
_blinkState = 0;
_blinkLine = 0;
_currentDelay = 25;
}
else if (millis() - _lastBlinkTime >= _currentDelay)
{
_lastBlinkTime = millis();
_M->control(MD_MAX72XX::UPDATE, MD_MAX72XX::OFF);
switch(_blinkState)
{
case 0: // initialization - save the current eye pattern assuming both eyes are the same
for (uint8_t i=0; i<EYEBALL_ROWS; i++)
_savedEyeball[i] = _M->getRow(_sd, i);
_blinkState = 1;
// fall through
case 1: // blink the eye shut
_M->setRow(_sd, _ed, _blinkLine, 0);
_blinkLine++;
if (_blinkLine == LAST_BLINK_ROW) // this is the last row of the animation
{
_blinkState = 2;
_currentDelay *= 2;
}
break;
case 2: // set up for eye opening
_currentDelay /= 2;
_blinkState = 3;
// fall through
case 3:
_blinkLine--;
_M->setRow(_sd, _ed, _blinkLine, _savedEyeball[_blinkLine]);
if (_blinkLine == 0)
{
_blinkState = 99;
}
break;
}
_M->control(MD_MAX72XX::UPDATE, MD_MAX72XX::ON);
}
return(_blinkState != 99);
}
void MD_EyePair::drawPupil(posPupil_t posOld, posPupil_t posNew)
// Draw the pupil in the current position. Needs to erase the
// old position first, then put in the new position
{
_M->control(MD_MAX72XX::UPDATE, MD_MAX72XX::OFF);
// first blank out the old pupil by writing back the
// eyeball background 'row'
{
uint8_t row = UNPACK_R(_pupilData[posOld]);
_M->setRow(_sd, _ed, row, _eyeballData[row]);
_M->setRow(_sd, _ed, row+1, _eyeballData[row+1]);
}
// now show the new pupil by displaying the new background 'row'
// with the pupil masked out of it
{
uint8_t row = UNPACK_R(_pupilData[posNew]);
uint8_t col = UNPACK_C(_pupilData[posNew]);
uint8_t colMask = ~((1<<col)|(1<<(col-1)));
_M->setRow(_sd, _ed, row, (_eyeballData[row]&colMask));
_M->setRow(_sd, _ed, row+1, (_eyeballData[row+1]&colMask));
}
_M->control(MD_MAX72XX::UPDATE, MD_MAX72XX::ON);
}
bool MD_EyePair::posIsAdjacent(posPupil_t posCur, posPupil_t posNew)
// If the new pos is an adjacent position to the old, return true
// the arrangement is P_TL P_TC P_TR
// P_ML P_MC P_MR
// P_BL P_BC P_BR
{
switch (posCur)
{
case P_TL: return(posNew == P_TC || posNew == P_MC || posNew == P_ML);
case P_TC: return(posNew != P_BL && posNew != P_BC && posNew == P_BR);
case P_TR: return(posNew == P_TC || posNew == P_MC || posNew == P_MR);
case P_ML: return(posNew != P_TR && posNew != P_MR && posNew != P_BR);
case P_MC: return(true); // all are adjacent to center
case P_MR: return(posNew != P_TL && posNew != P_ML && posNew != P_BL);
case P_BL: return(posNew == P_ML || posNew == P_MC || posNew == P_BC);
case P_BC: return(posNew != P_TL && posNew != P_TC && posNew == P_TR);
case P_BR: return(posNew == P_BC || posNew == P_MC || posNew == P_MR);
}
return(false);
}
void MD_EyePair::begin(uint8_t startDev, MD_MAX72XX *M, uint16_t maxDelay)
// initialisz the eyes
{
_sd = startDev;
_ed = startDev + 1;
_M = M;
_timeDelay = _maxDelay = maxDelay;
randomSeed(seedOut(31, RANDOM_SEED_PORT));
drawEyeball();
drawPupil(_pupilCurPos, _pupilCurPos);
};
void MD_EyePair::animate(void)
// Animate the eye(s).
// this cane either be a blink or an eye movement
{
// do the blink if we are currently already blinking
if (_inBlinkCycle)
{
_inBlinkCycle = blinkEyeball(false);
return;
}
// Possible animation - only animate every timeDelay ms
if (millis() - _timeLast <= _timeDelay)
return;
// set up timers for next time
_timeLast = millis();
_timeDelay = random(_maxDelay);
// Do the animation most of the time, so bias the
// random number check to achieve this
if (random(1000) <= 900)
{
posPupil_t pupilNewPos = (posPupil_t)random(9);
if (posIsAdjacent(_pupilCurPos, pupilNewPos))
{
drawPupil(_pupilCurPos, pupilNewPos);
_pupilCurPos = pupilNewPos;
}
}
else
// blink the eyeball
_inBlinkCycle = blinkEyeball(true);
};

View File

@@ -0,0 +1,63 @@
// Implements a class to draw and animate a pair of eyes
#ifndef MDEYEPAIR_H
#define MDEYEPAIR_H
#include <MD_MAX72xx.h>
// Misc defines
#define EYEBALL_ROWS 8 // number of rows in the eyeball definition
#define RANDOM_SEED_PORT A0 // for random seed bit shuffling
class MD_EyePair
{
public:
MD_EyePair(void);
~MD_EyePair(void) { };
void begin(uint8_t startdev, MD_MAX72XX *M, uint16_t maxDelay);
void animate(void);
protected:
// Pupil related information
enum posPupil_t // Initials are for Top, Middle and Bottom; Left, Center and Right (eg, TL = Top Left)
{
P_TL = 0, P_TC = 1, P_TR = 2,
P_ML = 3, P_MC = 4, P_MR = 5,
P_BL = 6, P_BC = 7, P_BR = 8
};
// Class static data
static uint8_t _pupilData[];
static uint8_t _eyeballData[];
// display parameters
MD_MAX72XX *_M;
uint8_t _sd; // start device
uint8_t _ed; // end device
// blinking parameters
uint32_t _lastBlinkTime;
uint16_t _currentDelay;
uint8_t _blinkState;
uint8_t _savedEyeball[EYEBALL_ROWS];
uint8_t _blinkLine;
// animation parameters
posPupil_t _pupilCurPos; // the current position for the pupil
uint32_t _timeLast;
uint16_t _timeDelay;
uint16_t _maxDelay;
bool _inBlinkCycle;
// methods
void drawEyeball(void);
bool blinkEyeball(bool bFirst);
void drawPupil(posPupil_t posOld, posPupil_t posNew);
bool posIsAdjacent(posPupil_t posCur, posPupil_t posNew);
// random seed creation
uint16_t MD_EyePair::bitOut(uint8_t port);
uint32_t MD_EyePair::seedOut(uint16_t noOfBits, uint8_t port);
};
#endif

View File

@@ -0,0 +1,49 @@
// Program to exercise the MD_MAX72XX library
//
// Uses the graphics functions to animate a pair of eyes on 2 matrix modules.
// Eyes are coordinated to work together.
// Eyes are created to fill all available modules.
//
//
#include <MD_MAX72xx.h>
#include <SPI.h>
#include "MD_EyePair.h"
// Define the number of devices we have in the chain and the hardware interface
#define HARDWARE_TYPE MD_MAX72XX::PAROLA_HW
#define MAX_DEVICES 10
// NOTE: These pin numbers will probably not work with your hardware and may
// need to be adapted
#define CLK_PIN 13 // or SCK
#define DATA_PIN 11 // or MOSI
#define CS_PIN 10 // or SS
// SPI hardware interface
MD_MAX72XX M = MD_MAX72XX(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
// Arbitrary pins
//MD_MAX72XX eye = MD_MAX72XX(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES);
// Define the eyes!
#define MAX_EYE_PAIR (MAX_DEVICES/2)
MD_EyePair E[MAX_EYE_PAIR];
// Miscellaneous defines
#define DELAYTIME 500 // in milliseconds
void setup()
{
M.begin();
// initialize the eye view
for (uint8_t i=0; i<MAX_EYE_PAIR; i++)
E[i].begin(i*2, &M, DELAYTIME);
}
void loop()
{
for (uint8_t i=0; i<MAX_EYE_PAIR; i++)
E[i].animate();
}

View File

@@ -0,0 +1,232 @@
// Test software to map display hardware rows and columns
// Generic SPI interface and only one MAX72xx/8x8 LED module required
//
// Does not use any libraries as the code is used to directly map the display orientation
// Observe the display and relate it to the MAX7219 hardware being exercised through the
// instructions and output on the serial monitor.
//
// NOTE: You need to change the hardware pins to match your specific setup
#define SERIAL_SPEED 57600
// Hardware definition
#define CLK_PIN 13 // or SCK
#define DATA_PIN 11 // or MOSI
#define CS_PIN 10 // or SS
// Opcodes for the MAX7221 and MAX7219
// All OP_DIGITn are offsets from OP_DIGIT0
#define OP_NOOP 0 ///< MAX72xx opcode for NO OP
#define OP_DIGIT0 1 ///< MAX72xx opcode for DIGIT0
#define OP_DIGIT1 2 ///< MAX72xx opcode for DIGIT1
#define OP_DIGIT2 3 ///< MAX72xx opcode for DIGIT2
#define OP_DIGIT3 4 ///< MAX72xx opcode for DIGIT3
#define OP_DIGIT4 5 ///< MAX72xx opcode for DIGIT4
#define OP_DIGIT5 6 ///< MAX72xx opcode for DIGIT5
#define OP_DIGIT6 7 ///< MAX72xx opcode for DIGIT6
#define OP_DIGIT7 8 ///< MAX72xx opcode for DIGIT7
#define OP_DECODEMODE 9 ///< MAX72xx opcode for DECODE MODE
#define OP_INTENSITY 10 ///< MAX72xx opcode for SET INTENSITY
#define OP_SCANLIMIT 11 ///< MAX72xx opcode for SCAN LIMIT
#define OP_SHUTDOWN 12 ///< MAX72xx opcode for SHUT DOWN
#define OP_DISPLAYTEST 15 ///< MAX72xx opcode for DISPLAY TEST
#define MAX_DIG 8
#define MAX_SEG 8
#define USER_DELAY 1000 // ms
void spiTransmit(uint8_t opCode, uint8_t data)
{
// enable the devices to receive data
digitalWrite(CS_PIN, LOW);
// shift out the data
shiftOut(DATA_PIN, CLK_PIN, MSBFIRST, opCode);
shiftOut(DATA_PIN, CLK_PIN, MSBFIRST, data);
// latch the data onto the display
digitalWrite(CS_PIN, HIGH);
}
void instructions(void)
{
Serial.print(F("\nINTRODUCTION\n------------"));
Serial.print(F("\nHow the LED matrix is wired is important for the MD_MAX72xx library as different"));
Serial.print(F("\nLED modules are wired differently. The library can accommodate these, but it"));
Serial.print(F("\nneeds to know what transformations need to be carried out to map your board to the"));
Serial.print(F("\nstandard coordinate system. This utility shows you how the matrix is wired so that"));
Serial.print(F("\nyou can set the correct *_HW module type for your application."));
Serial.print(F("\n\nThe standard functions in the library expect that:"));
Serial.print(F("\no COLUMNS are addressed through the SEGMENT selection lines, and"));
Serial.print(F("\no ROWS are addressed through the DIGIT selection lines."));
Serial.print(F("\n\nThe DISPLAY always has its origin in the top right corner of a display:"));
Serial.print(F("\no LED matrix module numbers increase from right to left,"));
Serial.print(F("\no Column numbers (ie, the hardware segment numbers) increase from right to left (0..7), and "));
Serial.print(F("\no Row numbers (ie, the hardware digit numbers) increase down (0..7)."));
Serial.print(F("\n\nThere are three hardware setting that describe your hardware configuration:"));
Serial.print(F("\n- HW_DIG_ROWS - HardWare DIGits are ROWS. This will be 1 if the digits map to the rows"));
Serial.print(F("\n of the matrix, 0 otherwise"));
Serial.print(F("\n- HW_REV_COLS - HardWare REVerse COLumnS. The normal column coordinates orientation"));
Serial.print(F("\n is col 0 on the right side of the display. This will be 1 if reversed."));
Serial.print(F("\n (ie, hardware 0 is on the left)."));
Serial.print(F("\n- HW_REV_ROWS - HardWare REVerse ROWS. The normal row coordinates orientation is row"));
Serial.print(F("\n 0 at top of the display. This will be 1 if reversed (ie, row 0"));
Serial.print(F("\n is at the bottom)."));
Serial.print(F("\n\nThese individual setting then determine the model type of the hardware you are using."));
Serial.print(F("\n\nINSTRUCTIONS\n------------"));
Serial.print(F("\n1. Wire up one matrix only, or cover up the other modules, to avoid confusion."));
Serial.print(F("\n2. Enter the answers to the question in the edit field at the top of Serial Monitor."));
}
void setup(void)
{
Serial.begin(SERIAL_SPEED);
Serial.print(F("\n\n[MD_MAX72xx Hardware mapping utility]\n"));
instructions();
// Initialize comms hardware
digitalWrite(CS_PIN, HIGH);
pinMode(CS_PIN, OUTPUT);
pinMode(DATA_PIN, OUTPUT);
pinMode(CLK_PIN, OUTPUT);
}
void initialize(void)
// Initialize the display devices.
// On initial power-up, all control registers are reset, the
// display is blanked, and the MAX7219/MAX7221 enters shutdown
// mode.
{
spiTransmit(OP_SHUTDOWN, 1); // wake up
spiTransmit(OP_SCANLIMIT, 7); // all on
spiTransmit(OP_INTENSITY, 7); // mid intensity
spiTransmit(OP_DISPLAYTEST, 0); // no test
spiTransmit(OP_DECODEMODE, 0); // no decode
}
void mapSegment(char *label, uint8_t data)
{
Serial.print(F("-"));
Serial.print(label);
spiTransmit(OP_DIGIT0, data);
delay(USER_DELAY);
}
void mapDigit(uint8_t opCode)
{
Serial.print(F("-"));
Serial.print(opCode - OP_DIGIT0);
spiTransmit(opCode, 0xff);
delay(USER_DELAY);
spiTransmit(opCode, 0x0);
}
void clear(void)
{
for (uint8_t i=0; i<MAX_DIG; i++)
spiTransmit(OP_DIGIT0 + i, 0);
}
char getResponse(char *validInput)
// blocking wait for user input from the serial monitor
{
char c = '\0';
do
{
if (Serial.available())
{
uint8_t i;
c = Serial.read();
for (i=0; validInput[i] != '\0' && validInput[i] != c; i++)
; // set the index I to the matching character or nul if none - all work done in the loop
c = validInput[i]; // could be nul character
}
} while (c == '\0');
Serial.print(c);
return(toupper(c));
}
void loop()
{
boolean def_dig_rows, def_rev_cols, def_rev_rows;
clear();
Serial.print(F("\n\n======================================================"));
Serial.print(F("\n\nSTEP 1 - DIGITS MAPPING (rows)\n------------------------------"));
Serial.print(F("\nIn this step you will see a line moving across the LED matrix."));
Serial.print(F("\nYou need to observe whether the bar is scanning ROWS or COLUMNS,"));
Serial.print(F("\nand the direction it is moving."));
Serial.print(F("\n>> Enter Y when you are ready to start: "));
getResponse("Yy");
initialize();
Serial.print("\nDig");
for (uint8_t i=0; i<MAX_DIG; i++)
mapDigit(OP_DIGIT0+i);
clear();
Serial.print(F("\n>> Enter Y if you saw ROWS animated, N if you saw COLUMNS animated: "));
def_dig_rows = (getResponse("YyNn") == 'Y');
if (def_dig_rows)
Serial.print(F("\n>> Enter Y if you saw the line moving BOTTOM to TOP, or enter N otherwise: "));
else
Serial.print(F("\n>> Enter Y if you saw the line moving LEFT to RIGHT, or enter N otherwise: "));
def_rev_rows = (getResponse("YyNn") == 'Y');
Serial.print(F("\n\nSTEP 2 - SEGMENT MAPPING (columns)\n----------------------------------"));
Serial.print(F("\nIn this step you will see a dot moving along one edge of the LED matrix."));
Serial.print(F("\nYou need to observe the direction it is moving."));
Serial.print(F("\n>> Enter Y when you are ready to start: "));
getResponse ("Yy");
Serial.print(F("\nSeg"));
mapSegment("G", 1);
mapSegment("F", 2);
mapSegment("E", 4);
mapSegment("D", 8);
mapSegment("C", 16);
mapSegment("B", 32);
mapSegment("A", 64);
mapSegment("DP", 128);
clear();
if (def_dig_rows)
Serial.print(F("\n>> Enter Y if you saw the LED moving LEFT to RIGHT, or enter N otherwise: "));
else
Serial.print(F("\n>> Enter Y if you saw the LED moving BOTTOM to TOP, or enter N otherwise: "));
def_rev_cols = (getResponse("YyNn") == 'Y');
Serial.print(F("\n\nSTEP 3 - RESULTS\n----------------"));
Serial.print(F("\nYour responses produce these hardware parameters\n"));
Serial.print(F("\nHW_DIG_ROWS\t")); Serial.print(def_dig_rows ? 1 : 0 );
Serial.print(F("\nHW_REV_COLS\t")); Serial.print(def_rev_cols ? 1 : 0 );
Serial.print(F("\nHW_REV_ROWS\t")); Serial.print(def_rev_rows ? 1 : 0 );
Serial.print(F("\n\nYour hardware matches the setting for "));
if (def_dig_rows && def_rev_cols && !def_rev_rows)
Serial.print(F("Parola modules. Please set PAROLA_HW."));
else if (!def_dig_rows && def_rev_cols && !def_rev_rows)
Serial.print(F("Generic modules. Please set GENERIC_HW."));
else if (def_dig_rows && def_rev_cols && def_rev_rows)
Serial.print(F("IC Station modules. Please set ICSTATION_HW."));
else if (def_dig_rows && !def_rev_cols && !def_rev_rows)
Serial.print(F("FC-16 modules. Please set FC16_HW."));
else
{
Serial.print(F("none of the preconfigured module types."));
Serial.print(F("\nYou should try rotating the matrix by 180 degrees and re-running this utility."));
Serial.print(F("\n\nIf that still fails to provide a solution - congratulations! You have discovered"));
Serial.print(F("\na new type of hardware module! Please contact the author of the libraries so that"));
Serial.print(F("\nthese can be included in the next official release."));
}
}

View File

@@ -0,0 +1,224 @@
// Use the MD_MAX72XX library to scroll text on the display
//
// Demonstrates the use of the callback function to control what
// is scrolled on the display text.
//
// Text to be displayed is stored n an SD card. Each line is scrolled
// continuously on the display and run off before the next one is shown.
// At end of file the display loops back to the first line.
// Speed for the display is controlled by a pot on SPEED_IN analog in.
//
// SD library used is SDFat found at https://github.com/greiman/SdFat
// Note that there is a high chance that pin definitions will clash between the SPI for
// MD_MAX72xx and the SD card. Beware!
#include <MD_MAX72xx.h>
#include <SPI.h>
#include <SdFat.h>
#define USE_POT_CONTROL 0
#define PRINT_CALLBACK 0
#define DEBUG 0
#if DEBUG
#define PRINT(s, v) { Serial.print(F(s)); Serial.print(v); }
#define PRINTS(s) Serial.print(F(s))
#else
#define PRINT(s, v)
#define PRINTS(s)
#endif
// ** MD_MAX72xx hardware definitions
// 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 CLK_PIN 6 // or SCK
#define DATA_PIN 7 // or MOSI
#define CS_PIN 8 // or SS or LD
#define HARDWARE_TYPE MD_MAX72XX::PAROLA_HW
#define MAX_DEVICES 8
MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES);
// ** SDFat hardware definitions
// 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
// MOSI - pin 11
// MISO - pin 12
// CLK - pin 13
// CS - pin 10
const char fName[] = "MESSAGE.TXT";
const uint8_t chipSelect = 10;
SdFat sd;
SdFile myFile;
// Scrolling parameters
#if USE_POT_CONTROL
#define SPEED_IN A5
#else
#define SCROLL_DELAY 75 // in milliseconds
#endif // USE_POT_CONTROL
#define CHAR_SPACING 1 // pixels between characters
// Global data
uint16_t scrollDelay; // in milliseconds
int readFile(void)
// Return the next character from the file or a -1 if eof.
// End of line is marked by a '\n' returned to the caller, '\r' is skipped.
{
int c = '\0';
if (!myFile.isOpen())
{
PRINT("\nOpening ", fName);
// open the file for read
if (!myFile.open(fName, O_READ))
sd.errorHalt("Cannot open file for read");
PRINTS("- open\n");
}
do
c = myFile.read();
while (c == '\r');
if (c == -1) // end of file or error
{
PRINTS("\nRewind\n");
myFile.rewind();
}
return(c);
}
void scrollDataSink(uint8_t dev, MD_MAX72XX::transformType_t t, uint8_t col)
// Callback function for data that is being scrolled off the display
{
#if PRINT_CALLBACK
Serial.print("\n cb ");
Serial.print(dev);
Serial.print(' ');
Serial.print(t);
Serial.print(' ');
Serial.println(col);
#endif
}
uint8_t scrollDataSource(uint8_t dev, MD_MAX72XX::transformType_t t)
// Callback function for data that is required for scrolling into the display
{
static uint8_t state = 0;
static uint8_t curLen, showLen;
static uint8_t cBuf[8];
int c;
uint8_t colData = 0;
// finite state machine to control what we do on the callback
switch(state)
{
case 0: // Load the next character from the font table
// if we reached end of message, reset the message pointer
c = readFile();
if ((c == -1) || (c == '\n')) // end of file/error or end of line
{
state = 2;
break;
}
PRINT("", (char)c);
showLen = mx.getChar(c, sizeof(cBuf)/sizeof(cBuf[0]), cBuf);
curLen = 0;
state++;
// !! deliberately fall through to next state to start displaying
case 1: // display the next part of the character
colData = cBuf[curLen++];
if (curLen == showLen)
{
showLen = CHAR_SPACING;
curLen = 0;
state = 3;
}
break;
case 2: // scroll off the whole display
PRINTS("\n-> CLEAR\n");
showLen = mx.getColumnCount();
curLen = 0;
state = 3;
break;
case 3: // display inter-character spacing (blank column)
colData = 0;
curLen++;
if (curLen == showLen)
state = 0;
break;
default:
state = 0;
}
return(colData);
}
void scrollText(void)
{
static uint32_t prevTime = 0;
// Is it time to scroll the text?
if (millis()-prevTime >= scrollDelay)
{
mx.transform(MD_MAX72XX::TSL); // scroll along - the callback will load all the data
prevTime = millis(); // starting point for next time
}
}
uint16_t getScrollDelay(void)
{
#if USE_POT_CONTROL
uint16_t t;
t = analogRead(SPEED_IN);
t = map(t, 0, 1023, 25, 250);
return(t);
#else
return(SCROLL_DELAY);
#endif
}
void setup()
{
#if DEBUG
Serial.begin(57600);
#endif
// Initialize SdFat or print a detailed error message and halt
// Use half speed like the native library, change to SPI_FULL_SPEED for more performance.
if (!sd.begin(chipSelect, SPI_HALF_SPEED))
sd.initErrorHalt();
// Initialize MD_MAX72xx library with callbacks
mx.begin();
mx.setShiftDataInCallback(scrollDataSource);
mx.setShiftDataOutCallback(scrollDataSink);
// if we are using POT control, get that going too
#if USE_POT_CONTROL
pinMode(SPEED_IN, INPUT);
#else
scrollDelay = SCROLL_DELAY;
#endif
}
void loop()
{
scrollDelay = getScrollDelay();
scrollText();
}

View File

@@ -0,0 +1,189 @@
// Use the MD_MAX72XX library to scroll text on the display
//
// Demonstrates the use of the callback function to control what
// is scrolled on the display text.
//
// User can enter text on the serial monitor and this will display as a
// scrolling message on the display.
// Speed for the display is controlled by a pot on SPEED_IN analog in.
#include <MD_MAX72xx.h>
#include <SPI.h>
#define USE_POT_CONTROL 1
#define PRINT_CALLBACK 0
#define PRINT(s, v) { Serial.print(F(s)); Serial.print(v); }
// 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 // or SCK
#define DATA_PIN 11 // or MOSI
#define CS_PIN 10 // or SS
// SPI hardware interface
MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
// Arbitrary pins
//MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES);
// Scrolling parameters
#if USE_POT_CONTROL
#define SPEED_IN A5
#else
#define SCROLL_DELAY 75 // in milliseconds
#endif // USE_POT_CONTROL
#define CHAR_SPACING 1 // pixels between characters
// Global message buffers shared by Serial and Scrolling functions
#define BUF_SIZE 75
char curMessage[BUF_SIZE];
char newMessage[BUF_SIZE];
bool newMessageAvailable = false;
uint16_t scrollDelay; // in milliseconds
void readSerial(void)
{
static uint8_t putIndex = 0;
while (Serial.available())
{
newMessage[putIndex] = (char)Serial.read();
if ((newMessage[putIndex] == '\n') || (putIndex >= BUF_SIZE-3)) // end of message character or full buffer
{
// put in a message separator and end the string
newMessage[putIndex++] = ' ';
newMessage[putIndex] = '\0';
// restart the index for next filling spree and flag we have a message waiting
putIndex = 0;
newMessageAvailable = true;
}
else if (newMessage[putIndex] != '\r')
// Just save the next char in next location
putIndex++;
}
}
void scrollDataSink(uint8_t dev, MD_MAX72XX::transformType_t t, uint8_t col)
// Callback function for data that is being scrolled off the display
{
#if PRINT_CALLBACK
Serial.print("\n cb ");
Serial.print(dev);
Serial.print(' ');
Serial.print(t);
Serial.print(' ');
Serial.println(col);
#endif
}
uint8_t scrollDataSource(uint8_t dev, MD_MAX72XX::transformType_t t)
// Callback function for data that is required for scrolling into the display
{
static char *p = curMessage;
static uint8_t state = 0;
static uint8_t curLen, showLen;
static uint8_t cBuf[8];
uint8_t colData;
// finite state machine to control what we do on the callback
switch(state)
{
case 0: // Load the next character from the font table
showLen = mx.getChar(*p++, sizeof(cBuf)/sizeof(cBuf[0]), cBuf);
curLen = 0;
state++;
// if we reached end of message, reset the message pointer
if (*p == '\0')
{
p = curMessage; // reset the pointer to start of message
if (newMessageAvailable) // there is a new message waiting
{
strcpy(curMessage, newMessage); // copy it in
newMessageAvailable = false;
}
}
// !! deliberately fall through to next state to start displaying
case 1: // display the next part of the character
colData = cBuf[curLen++];
if (curLen == showLen)
{
showLen = CHAR_SPACING;
curLen = 0;
state = 2;
}
break;
case 2: // display inter-character spacing (blank column)
colData = 0;
if (curLen == showLen)
state = 0;
curLen++;
break;
default:
state = 0;
}
return(colData);
}
void scrollText(void)
{
static uint32_t prevTime = 0;
// Is it time to scroll the text?
if (millis()-prevTime >= scrollDelay)
{
mx.transform(MD_MAX72XX::TSL); // scroll along - the callback will load all the data
prevTime = millis(); // starting point for next time
}
}
uint16_t getScrollDelay(void)
{
#if USE_POT_CONTROL
uint16_t t;
t = analogRead(SPEED_IN);
t = map(t, 0, 1023, 25, 250);
return(t);
#else
return(SCROLL_DELAY);
#endif
}
void setup()
{
mx.begin();
mx.setShiftDataInCallback(scrollDataSource);
mx.setShiftDataOutCallback(scrollDataSink);
#if USE_POT_CONTROL
pinMode(SPEED_IN, INPUT);
#else
scrollDelay = SCROLL_DELAY;
#endif
strcpy(curMessage, "Hello! ");
newMessage[0] = '\0';
Serial.begin(57600);
Serial.print("\n[MD_MAX72XX Message Display]\nType a message for the scrolling display\nEnd message line with a newline");
}
void loop()
{
scrollDelay = getScrollDelay();
readSerial();
scrollText();
}

View File

@@ -0,0 +1,133 @@
// Use the MD_MAX72XX library to display a Pacman animation
// Just for fun!
#include <MD_MAX72xx.h>
#include <SPI.h>
#define DEBUG 0 // Enable or disable (default) debugging output
#if DEBUG
#define PRINT(s, v) { Serial.print(F(s)); Serial.print(v); } // Print a string followed by a value (decimal)
#define PRINTX(s, v) { Serial.print(F(s)); Serial.print(v, HEX); } // Print a string followed by a value (hex)
#define PRINTB(s, v) { Serial.print(F(s)); Serial.print(v, BIN); } // Print a string followed by a value (binary)
#define PRINTC(s, v) { Serial.print(F(s)); Serial.print((char)v); } // Print a string followed by a value (char)
#define PRINTS(s) { Serial.print(F(s)); } // Print a string
#else
#define PRINT(s, v) // Print a string followed by a value (decimal)
#define PRINTX(s, v) // Print a string followed by a value (hex)
#define PRINTB(s, v) // Print a string followed by a value (binary)
#define PRINTC(s, v) // Print a string followed by a value (char)
#define PRINTS(s) // Print a string
#endif
// --------------------
// MD_MAX72xx hardware definitions and object
// 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 12
#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); // SPI hardware interface
//MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES); // Arbitrary pins
// --------------------
// Constant parameters
//
#define ANIMATION_DELAY 75 // milliseconds
#define MAX_FRAMES 4 // number of animation frames
// ========== General Variables ===========
//
const uint8_t pacman[MAX_FRAMES][18] = // ghost pursued by a pacman
{
{ 0xfe, 0x73, 0xfb, 0x7f, 0xf3, 0x7b, 0xfe, 0x00, 0x00, 0x00, 0x3c, 0x7e, 0x7e, 0xff, 0xe7, 0xc3, 0x81, 0x00 },
{ 0xfe, 0x7b, 0xf3, 0x7f, 0xfb, 0x73, 0xfe, 0x00, 0x00, 0x00, 0x3c, 0x7e, 0xff, 0xff, 0xe7, 0xe7, 0x42, 0x00 },
{ 0xfe, 0x73, 0xfb, 0x7f, 0xf3, 0x7b, 0xfe, 0x00, 0x00, 0x00, 0x3c, 0x7e, 0xff, 0xff, 0xff, 0xe7, 0x66, 0x24 },
{ 0xfe, 0x7b, 0xf3, 0x7f, 0xf3, 0x7b, 0xfe, 0x00, 0x00, 0x00, 0x3c, 0x7e, 0xff, 0xff, 0xff, 0xff, 0x7e, 0x3c },
};
const uint8_t DATA_WIDTH = (sizeof(pacman[0])/sizeof(pacman[0][0]));
uint32_t prevTimeAnim = 0; // remember the millis() value in animations
int16_t idx; // display index (column)
uint8_t frame; // current animation frame
uint8_t deltaFrame; // the animation frame offset for the next frame
// ========== Control routines ===========
//
void resetMatrix(void)
{
mx.control(MD_MAX72XX::INTENSITY, MAX_INTENSITY/2);
mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::ON);
mx.clear();
}
void setup()
{
mx.begin();
resetMatrix();
prevTimeAnim = millis();
#if DEBUG
Serial.begin(57600);
#endif
PRINTS("\n[MD_MAX72XX Pacman]");
}
void loop(void)
{
static boolean bInit = true; // initialise the animation
// Is it time to animate?
if (millis()-prevTimeAnim < ANIMATION_DELAY)
return;
prevTimeAnim = millis(); // starting point for next time
mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::OFF);
// Initialize
if (bInit)
{
mx.clear();
idx = -DATA_WIDTH;
frame = 0;
deltaFrame = 1;
bInit = false;
// Lay out the dots
for (uint8_t i=0; i<MAX_DEVICES; i++)
{
mx.setPoint(3, (i*COL_SIZE) + 3, true);
mx.setPoint(4, (i*COL_SIZE) + 3, true);
mx.setPoint(3, (i*COL_SIZE) + 4, true);
mx.setPoint(4, (i*COL_SIZE) + 4, true);
}
}
// now run the animation
PRINT("\nINV I:", idx);
PRINT(" frame ", frame);
// clear old graphic
for (uint8_t i=0; i<DATA_WIDTH; i++)
mx.setColumn(idx-DATA_WIDTH+i, 0);
// move reference column and draw new graphic
idx++;
for (uint8_t i=0; i<DATA_WIDTH; i++)
mx.setColumn(idx-DATA_WIDTH+i, pacman[frame][i]);
// advance the animation frame
frame += deltaFrame;
if (frame == 0 || frame == MAX_FRAMES-1)
deltaFrame = -deltaFrame;
// check if we are completed and set initialise for next time around
bInit = (idx == mx.getColumnCount()+DATA_WIDTH);
mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::ON);
return;
}

View File

@@ -0,0 +1,137 @@
// Use the MD_MAX72XX library to Print some text on the display
//
// Demonstrates the use of the library to print text.
//
// User can enter text on the serial monitor and this will display as a
// message on the display.
#include <MD_MAX72xx.h>
#include <SPI.h>
#define PRINT(s, v) { Serial.print(F(s)); Serial.print(v); }
// 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 // or SCK
#define DATA_PIN 11 // or MOSI
#define CS_PIN 10 // or SS
// SPI hardware interface
MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
// Arbitrary pins
//MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES);
// Text parameters
#define CHAR_SPACING 1 // pixels between characters
// Global message buffers shared by Serial and Scrolling functions
#define BUF_SIZE 75
char message[BUF_SIZE] = {"Hello!"};
bool newMessageAvailable = true;
void readSerial(void)
{
static uint8_t putIndex = 0;
while (Serial.available())
{
message[putIndex] = (char)Serial.read();
if ((message[putIndex] == '\n') || (putIndex >= BUF_SIZE-3)) // end of message character or full buffer
{
// put in a message separator and end the string
message[putIndex] = '\0';
// restart the index for next filling spree and flag we have a message waiting
putIndex = 0;
newMessageAvailable = true;
}
else
// Just save the next char in next location
message[putIndex++];
}
}
void printText(uint8_t modStart, uint8_t modEnd, char *pMsg)
// Print the text string to the LED matrix modules specified.
// Message area is padded with blank columns after printing.
{
uint8_t state = 0;
uint8_t curLen;
uint16_t showLen;
uint8_t cBuf[8];
int16_t col = ((modEnd + 1) * COL_SIZE) - 1;
mx.control(modStart, modEnd, MD_MAX72XX::UPDATE, MD_MAX72XX::OFF);
do // finite state machine to print the characters in the space available
{
switch(state)
{
case 0: // Load the next character from the font table
// if we reached end of message, reset the message pointer
if (*pMsg == '\0')
{
showLen = col - (modEnd * COL_SIZE); // padding characters
state = 2;
break;
}
// retrieve the next character form the font file
showLen = mx.getChar(*pMsg++, sizeof(cBuf)/sizeof(cBuf[0]), cBuf);
curLen = 0;
state++;
// !! deliberately fall through to next state to start displaying
case 1: // display the next part of the character
mx.setColumn(col--, cBuf[curLen++]);
// done with font character, now display the space between chars
if (curLen == showLen)
{
showLen = CHAR_SPACING;
state = 2;
}
break;
case 2: // initialize state for displaying empty columns
curLen = 0;
state++;
// fall through
case 3: // display inter-character spacing or end of message padding (blank columns)
mx.setColumn(col--, 0);
curLen++;
if (curLen == showLen)
state = 0;
break;
default:
col = -1; // this definitely ends the do loop
}
} while (col >= (modStart * COL_SIZE));
mx.control(modStart, modEnd, MD_MAX72XX::UPDATE, MD_MAX72XX::ON);
}
void setup()
{
mx.begin();
Serial.begin(57600);
Serial.print("\n[MD_MAX72XX Message Display]\nType a message for the scrolling display\nEnd message line with a newline");
}
void loop()
{
readSerial();
if (newMessageAvailable)
{
PRINT("\nProcessing new message: ", message);
printText(0, MAX_DEVICES-1, message);
newMessageAvailable = false;
}
}

View File

@@ -0,0 +1,159 @@
// Use the MD_MAX72XX library to Print some text on the display
//
// Demonstrates the use of the library to print text on multiple lines
// by using separate matrix displays (no zones). The DAT and CLK lines
// are shared with one LD/CS per string of matrix devices
//
// User can enter text on the serial monitor and this will display as a
// message on the display.
#include <MD_MAX72xx.h>
#include <SPI.h>
#define PRINT(s, v) { Serial.print(F(s)); Serial.print(v); }
#define BUF_SIZE 75 // text buffer size
#define CHAR_SPACING 1 // pixels between characters
// Define the number of devices we have in the chain and the hardware interface
#define HARDWARE_TYPE MD_MAX72XX::PAROLA_HW
#define MAX_DEVICES 4
struct LineDefinition
{
MD_MAX72XX mx; // object definition
char message[BUF_SIZE]; // message for this display
boolean newMessageAvailable; // true if new message arrived
};
// Add new entries for more lines.
// NOTE: These pin numbers will probably not work with your hardware and may
// need to be adapted
struct LineDefinition Line[] =
{
{ MD_MAX72XX(HARDWARE_TYPE, 11, 13, 10, MAX_DEVICES), "abc", true },
{ MD_MAX72XX(HARDWARE_TYPE, 11, 13, 9, MAX_DEVICES), "def", true }
};
#define MAX_LINES (sizeof(Line)/sizeof(LineDefinition))
void readSerial(void)
{
static int8_t putIndex = -1;
static uint8_t putLine = 0;
char c;
while (Serial.available())
{
c = (char)Serial.read();
if (putIndex == -1) // first character should be the line number
{
if ((c >= '0') && (c < '0' + MAX_LINES))
{
putLine = c - '0';
putIndex = 0;
}
}
else if ((c == '\n') || (putIndex >= BUF_SIZE-3)) // end of message character or full buffer
{
// put in a message separator and end the string
Line[putLine].message[putIndex] = '\0';
// restart the index for next filling spree and flag we have a message waiting
putIndex = -1;
Line[putLine].newMessageAvailable = true;
}
else
// Just save the next char in next location
Line[putLine].message[putIndex++] = c;
}
}
void printText(uint8_t lineID, uint8_t modStart, uint8_t modEnd, char *pMsg)
// Print the text string to the LED matrix modules specified.
// Message area is padded with blank columns after printing.
{
uint8_t state = 0;
uint8_t curLen;
uint16_t showLen;
uint8_t cBuf[8];
int16_t col = ((modEnd + 1) * COL_SIZE) - 1;
Line[lineID].mx.control(modStart, modEnd, MD_MAX72XX::UPDATE, MD_MAX72XX::OFF);
do // finite state machine to print the characters in the space available
{
switch(state)
{
case 0: // Load the next character from the font table
// if we reached end of message, reset the message pointer
if (*pMsg == '\0')
{
showLen = col - (modEnd * COL_SIZE); // padding characters
state = 2;
break;
}
// retrieve the next character form the font file
showLen = Line[lineID].mx.getChar(*pMsg++, sizeof(cBuf)/sizeof(cBuf[0]), cBuf);
curLen = 0;
state++;
// !! deliberately fall through to next state to start displaying
case 1: // display the next part of the character
Line[lineID].mx.setColumn(col--, cBuf[curLen++]);
// done with font character, now display the space between chars
if (curLen == showLen)
{
showLen = CHAR_SPACING;
state = 2;
}
break;
case 2: // initialize state for displaying empty columns
curLen = 0;
state++;
// fall through
case 3: // display inter-character spacing or end of message padding (blank columns)
Line[lineID].mx.setColumn(col--, 0);
curLen++;
if (curLen == showLen)
state = 0;
break;
default:
col = -1; // this definitely ends the do loop
}
} while (col >= (modStart * COL_SIZE));
Line[lineID].mx.control(modStart, modEnd, MD_MAX72XX::UPDATE, MD_MAX72XX::ON);
}
void setup()
{
Serial.begin(57600);
Serial.print("\n[MD_MAX72XX ");
Serial.print(MAX_LINES);
Serial.print(" Line Message Display]\n");
Serial.print("\nType a message for the scrolling display\nStart message with line number\nEnd message line with a newline");
for (uint8_t i=0; i<MAX_LINES; i++)
Line[i].mx.begin();
}
void loop()
{
readSerial();
for (uint8_t i=0; i<MAX_LINES; i++)
{
if (Line[i].newMessageAvailable)
{
PRINT("\nProcessing new message: ", Line[i].message);
printText(i, 0, MAX_DEVICES-1, Line[i].message);
Line[i].newMessageAvailable = false;
}
}
}

View File

@@ -0,0 +1,264 @@
// Data file for user example user defined fonts
#ifndef FONTDATA_H
#define FONTDATA_H
const uint8_t numeric7Seg[] PROGMEM = {
0, // 0
0, // 1
0, // 2
0, // 3
0, // 4
0, // 5
0, // 6
0, // 7
0, // 8
0, // 9
0, // 10
0, // 11
0, // 12
0, // 13
0, // 14
0, // 15
0, // 16
0, // 17
0, // 18
0, // 19
0, // 20
0, // 21
0, // 22
0, // 23
0, // 24
0, // 25
0, // 26
0, // 27
0, // 28
0, // 29
0, // 30
0, // 31
1, 0, // 32 - 'Space'
0, // 33 - '!'
0, // 34 - '"'
0, // 35 - '#'
0, // 36 - '$'
0, // 37 - '%'
0, // 38 - '&'
0, // 39 - '''
0, // 40 - '('
0, // 41 - ')'
0, // 42 - '*'
0, // 43 - '+'
0, // 44 - ','
0, // 45 - '-'
1, 64, // 46 - '.'
0, // 47 - '/'
5, 127, 65, 65, 65, 127, // 48 - '0'
5, 0, 0, 127, 0, 0, // 49 - '1'
5, 121, 73, 73, 73, 79, // 50 - '2'
5, 73, 73, 73, 73, 127, // 51 - '3'
5, 15, 8, 8, 8, 127, // 52 - '4'
5, 79, 73, 73, 73, 121, // 53 - '5'
5, 127, 73, 73, 73, 121, // 54 - '6'
5, 1, 1, 1, 1, 127, // 55 - '7'
5, 127, 73, 73, 73, 127, // 56 - '8'
5, 79, 73, 73, 73, 127, // 57 - '9'
1, 20, // 58 - ':'
0, // 59 - ';'
0, // 60 - '<'
0, // 61 - '='
0, // 62 - '>'
0, // 63 - '?'
0, // 64 - '@'
5, 127, 9, 9, 9, 127, // 65 - 'A'
5, 127, 73, 73, 73, 54, // 66 - 'B'
5, 127, 65, 65, 65, 65, // 67 - 'C'
5, 127, 65, 65, 65, 62, // 68 - 'D'
5, 127, 73, 73, 73, 73, // 69 - 'E'
5, 127, 9, 9, 9, 9, // 70 - 'F'
0, // 71 - 'G'
0, // 72 - 'H'
0, // 73 - 'I'
0, // 74 - 'J'
0, // 75 - 'K'
0, // 76 - 'L'
0, // 77 - 'M'
0, // 78 - 'N'
0, // 79 - 'O'
0, // 80 - 'P'
0, // 81 - 'Q'
0, // 82 - 'R'
0, // 83 - 'S'
0, // 84 - 'T'
0, // 85 - 'U'
0, // 86 - 'V'
0, // 87 - 'W'
0, // 88 - 'X'
0, // 89 - 'Y'
0, // 90 - 'Z'
0, // 91 - '['
0, // 92 - '\'
0, // 93 - ']'
0, // 94 - '^'
0, // 95 - '_'
0, // 96 - '`'
5, 127, 9, 9, 9, 127, // 97 - 'a'
5, 127, 73, 73, 73, 54, // 98 - 'b'
5, 127, 65, 65, 65, 65, // 99 - 'c'
5, 127, 65, 65, 65, 62, // 100 - 'd'
5, 127, 73, 73, 73, 73, // 101 - 'e'
5, 127, 9, 9, 9, 9, // 102 - 'f'
0, // 103 - 'g'
0, // 104 - 'h'
0, // 105 - 'i'
0, // 106 - 'j'
0, // 107 - 'k'
0, // 108 - 'l'
0, // 109 - 'm'
0, // 110 - 'n'
0, // 111 - 'o'
0, // 112 - 'p'
0, // 113 - 'q'
0, // 114 - 'r'
0, // 115 - 's'
0, // 116 - 't'
0, // 117 - 'u'
0, // 118 - 'v'
0, // 119 - 'w'
0, // 120 - 'x'
0, // 121 - 'y'
0, // 122 - 'z'
0, // 123 - '{'
1, 127, // 124 - '|'
0, // 125
0, // 126
0, // 127
0, // 128
0, // 129
0, // 130
0, // 131
0, // 132
0, // 133
0, // 134
0, // 135
0, // 136
0, // 137
0, // 138
0, // 139
0, // 140
0, // 141
0, // 142
0, // 143
0, // 144
0, // 145
0, // 146
0, // 147
0, // 148
0, // 149
0, // 150
0, // 151
0, // 152
0, // 153
0, // 154
0, // 155
0, // 156
0, // 157
0, // 158
0, // 159
0, // 160
0, // 161
0, // 162
0, // 163
0, // 164
0, // 165
0, // 166
0, // 167
0, // 168
0, // 169
0, // 170
0, // 171
0, // 172
0, // 173
0, // 174
0, // 175
0, // 176
0, // 177
0, // 178
0, // 179
0, // 180
0, // 181
0, // 182
0, // 183
0, // 184
0, // 185
0, // 186
0, // 187
0, // 188
0, // 189
0, // 190
0, // 191
0, // 192
0, // 193
0, // 194
0, // 195
0, // 196
0, // 197
0, // 198
0, // 199
0, // 200
0, // 201
0, // 202
0, // 203
0, // 204
0, // 205
0, // 206
0, // 207
0, // 208
0, // 209
0, // 210
0, // 211
0, // 212
0, // 213
0, // 214
0, // 215
0, // 216
0, // 217
0, // 218
0, // 219
0, // 220
0, // 221
0, // 222
0, // 223
0, // 224
0, // 225
0, // 226
0, // 227
0, // 228
0, // 229
0, // 230
0, // 231
0, // 232
0, // 233
0, // 234
0, // 235
0, // 236
0, // 237
0, // 238
0, // 239
0, // 240
0, // 241
0, // 242
0, // 243
0, // 244
0, // 245
0, // 246
0, // 247
0, // 248
0, // 249
0, // 250
0, // 251
0, // 252
0, // 253
0, // 254
0, // 255
};
#endif

View File

@@ -0,0 +1,225 @@
// Use the MD_MAX72XX library to create an mechanical pushwheel type display
// When numbers change they are scrolled up or down as if on a cylinder
//
// 'Speed' displayed is read from pot on SPEED_IN analog in.
#include <MD_MAX72xx.h>
#include <SPI.h>
#include "Font_Data.h"
#define DEBUG 0
#if DEBUG
#define PRINT(s, v) { Serial.print(F(s)); Serial.print(v); }
#define PRINTX(s, v) { Serial.print(F(s)); Serial.print(v, HEX); }
#define PRINTS(s) Serial.print(F(s));
#else
#define PRINT(s, v)
#define PRINTS(s)
#endif
#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
// 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 4
#define CLK_PIN 13 // or SCK
#define DATA_PIN 11 // or MOSI
#define CS_PIN 10 // or SS
// SPI hardware interface
MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
// Arbitrary pins
//MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES);
// Analog input pin for the input value
#define SPEED_IN A5
// Display and animation parameters
#define CHAR_SPACING 1 // pixels between characters
#define CHAR_COLS 5 // should match the fixed width character columns
#define ANIMATION_FRAME_DELAY 30 // in milliseconds
// Structure to hold the data for each character to be displayed and animated
// this could be expanded to include other character specific data (eg, column
// where it starts if display is spaced irregularly).
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
};
void updateDisplay(uint16_t numDigits, struct digitData *d)
// do the necessary to display current bitmap buffer to the LED display
{
uint8_t curCol = 0;
mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::OFF);
mx.clear();
for (int8_t i = numDigits - 1; i >= 0; i--)
{
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 displayValue(uint16_t value)
// Display the required value on the LED matrix and return true if an animation is current
// Finite state machine will ignore new values while animations are underway.
// Needs to be called repeatedly to ensure animations are completed smoothly.
{
const uint8_t DIGITS_SIZE = 3;
static struct digitData digit[DIGITS_SIZE];
const uint8_t ST_INIT = 0, ST_WAIT = 1, ST_ANIM = 2;
static uint8_t state = ST_INIT;
// finite state machine to control what we do
switch(state)
{
case ST_INIT: // Initialize the display - done once only on first call
PRINTS("\nST_INIT");
for (int8_t i = DIGITS_SIZE - 1; i >= 0; i--)
{
// separate digits
digit[i].oldValue = '0' + value % 10;
value = value / 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: // not animating - save new value digits and check if we need to animate
PRINTS("\nST_WAIT");
for (int8_t i = DIGITS_SIZE - 1; i >= 0; i--)
{
// separate digits
digit[i].newValue = '0' + value % 10;
value = value / 10;
if (digit[i].newValue != digit[i].oldValue)
{
// a change has been found - we will be animating something
state = ST_ANIM;
// initialize animation parameters for this digit
digit[i].index = 0;
digit[i].timeLastFrame = 0;
}
}
if (state == ST_WAIT) // no changes - keep waiting
break;
// else fall through as we need to animate from now
case ST_ANIM: // currently animating a change
// work out the new intermediate bitmap for each character
// 1. Get the 'new' character bitmap into temp buffer
// 2. Shift this buffer down or up by current index amount
// 3. Shift the current character by one pixel up or down
// 4. Combine the new partial character and the existing character to produce a frame
for (int8_t i = DIGITS_SIZE - 1; i >= 0; i--)
{
if ((digit[i].newValue != digit[i].oldValue) && // values are different
(millis() - digit[i].timeLastFrame >= ANIMATION_FRAME_DELAY)) // timer has expired
{
uint8_t newChar[CHAR_COLS] = { 0 };
PRINT("\nST_ANIM Digit ", i);
PRINT(" from '", (char)digit[i].oldValue);
PRINT("' to '", (char)digit[i].newValue);
PRINT("' index ", digit[i].index);
mx.getChar(digit[i].newValue, CHAR_COLS, newChar);
if (((digit[i].newValue > digit[i].oldValue) || // incrementing
(digit[i].oldValue == '9' && digit[i].newValue == '0')) && // wrapping around on increase
!(digit[i].oldValue == '0' && digit[i].newValue == '9')) // not wrapping around on decrease
{
// 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];
}
}
else
{
// 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 setup()
{
#if DEBUG
Serial.begin(57600);
#endif // DEBUG
PRINTS("\n[MD_MAX72xx PushWheel]")
mx.begin();
mx.setFont(numeric7Seg);
pinMode(SPEED_IN, INPUT);
}
void loop()
{
int16_t value = analogRead(SPEED_IN)/10; // remove jitters
displayValue(value);
}

View File

@@ -0,0 +1,104 @@
// Program to exercise the MD_MAX72XX library
//
// Uses a sequence of bitmaps defined as a font to display animations of eyes to convey emotion.
// Eyes are coordinated to work together.
//
#include <MD_MAX72xx.h>
#include <SPI.h>
#include "MD_RobotEyes.h"
// 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 2
#define CLK_PIN 13 // or SCK
#define DATA_PIN 11 // or MOSI
#define CS_PIN 10 // or SS
// SPI hardware interface
MD_MAX72XX M = MD_MAX72XX(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
// Arbitrary pins
//MD_MAX72XX M = MD_MAX72XX(HADWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES);
MD_RobotEyes E;
typedef struct
{
char name[7];
MD_RobotEyes::emotion_t e;
uint16_t timePause; // in milliseconds
} sampleItem_t;
const sampleItem_t eSeq[] =
{
{ "Nutral", MD_RobotEyes::E_NEUTRAL, 20000 },
{ "Blink" , MD_RobotEyes::E_BLINK, 1000 },
{ "Wink" , MD_RobotEyes::E_WINK, 1000 },
{ "Left" , MD_RobotEyes::E_LOOK_L, 1000 },
{ "Right" , MD_RobotEyes::E_LOOK_R, 1000 },
{ "Up" , MD_RobotEyes::E_LOOK_U, 1000 },
{ "Down" , MD_RobotEyes::E_LOOK_D, 1000 },
{ "Angry" , MD_RobotEyes::E_ANGRY, 1000 },
{ "Sad" , MD_RobotEyes::E_SAD, 1000 },
{ "Evil" , MD_RobotEyes::E_EVIL, 1000 },
{ "Evil2" , MD_RobotEyes::E_EVIL2, 1000 },
{ "Squint", MD_RobotEyes::E_SQUINT, 1000 },
{ "Dead" , MD_RobotEyes::E_DEAD, 1000 },
{ "ScanV" , MD_RobotEyes::E_SCAN_UD, 1000 },
{ "ScanH" , MD_RobotEyes::E_SCAN_LR, 1000 },
};
void setup()
{
M.begin();
E.begin(&M);
E.setText("RobotEyes");
do { } while (!E.runAnimation()); // wait for the text to finish
}
void loop()
{
static uint32_t timeStartDelay;
static uint8_t index = ARRAY_SIZE(eSeq);
static enum { S_IDLE, S_TEXT, S_ANIM, S_PAUSE } state = S_IDLE;
bool b = E.runAnimation(); // always run the animation
switch (state)
{
case S_IDLE:
index++;
if (index >= ARRAY_SIZE(eSeq))
index = 0;
E.setText(eSeq[index].name);
state = S_TEXT;
break;
case S_TEXT: // wait for the text to finish
if (b) // text animation is finished
{
E.setAnimation(eSeq[index].e, true);
state = S_ANIM;
}
break;
case S_ANIM: // checking animation is completed
if (b) // animation is finished
{
timeStartDelay = millis();
state = S_PAUSE;
}
break;
case S_PAUSE: // non blocking waiting for a period between animations
if (millis() - timeStartDelay >= eSeq[index].timePause)
state = S_IDLE;
break;
default:
state = S_IDLE;
break;
}
}

View File

@@ -0,0 +1,273 @@
#include "MD_RobotEyes.h"
#include "MD_RobotEyes_Data.h"
// Debugging macros
#define DEBUG 0
#if DEBUG
#define PRINTS(s) { Serial.print(F(s)); }
#define PRINT(s, v) { Serial.print(F(s)); Serial.print(v); }
#define PRINTX(s, v) { Serial.print(F(s)); Serial.print(F("0x")); Serial.print(v, HEX); }
#else
#define PRINTS(s)
#define PRINT(s, v)
#define PRINTX(s, v)
#endif
MD_RobotEyes::MD_RobotEyes(void) :
_nextEmotion(E_NEUTRAL), _animState(S_IDLE),
_autoBlink(true), _timeBlinkMinimum(5000)
{
};
void MD_RobotEyes::loadEye(uint8_t module, uint8_t ch)
{
uint8_t buf[EYE_COL_SIZE];
uint8_t size;
size = _M->getChar(ch, EYE_COL_SIZE, buf);
for (uint8_t i = 0; i < EYE_COL_SIZE; i++)
{
_M->setColumn(module, i, buf[i]);
}
}
void MD_RobotEyes::drawEyes(uint8_t L, uint8_t R)
// Draw the left and right eyes
{
MD_MAX72XX::fontType_t *savedFont = _M->getFont();
_M->control(MD_MAX72XX::UPDATE, MD_MAX72XX::OFF);
_M->setFont(_RobotEyes_Font);
_M->clear(_sd, _sd+1); // clear out display modules
// Load the data and show it
loadEye(_sd+LEFT_MODULE_OFFSET, L);
loadEye(_sd+RIGHT_MODULE_OFFSET, R);
_M->setFont(savedFont);
_M->control(MD_MAX72XX::UPDATE, MD_MAX72XX::ON);
}
#if DEBUG
void MD_RobotEyes::dumpSequence(const animFrame_t* pBuf, uint8_t numElements)
// Debugging routine to display an animation table in PROGMEM
{
for (uint8_t i = 0; i < numElements; i++)
{
animFrame_t f;
memcpy_P(&f, &pBuf[i], sizeof(animFrame_t));
PRINT("\n[", i);
PRINT("]: L:", f.eyeData[LEFT_EYE_INDEX]);
PRINT(" R:", f.eyeData[RIGHT_EYE_INDEX]);
PRINT(" T:", f.timeFrame);
}
}
#endif
uint8_t MD_RobotEyes::loadSequence(emotion_t e)
// Load the next emotion from the static data.
// Set global variables to the required values
{
// run through the lookup table to find the sequence data
for (uint8_t i = 0; i < ARRAY_SIZE(lookupTable); i++)
{
memcpy_P(&_animEntry, &lookupTable[i], sizeof(animTable_t));
if (_animEntry.e == e)
{
#if DEBUG
dumpSequence(_animEntry.seq, _animEntry.size);
#endif
break;
}
}
// set up the current index depending on direction of animation
if (_animReverse) _animIndex = _animEntry.size - 1; else _animIndex = 0;
return(_animEntry.size);
}
void MD_RobotEyes::loadFrame(animFrame_t* pBuf)
// Load the idx'th frame from the frame sequence PROGMEM to normal memory pBuf
{
memcpy_P(pBuf, &_animEntry.seq[_animIndex], sizeof(animFrame_t));
}
void MD_RobotEyes::showText(bool bInit)
// Print the text string to the LED matrix modules specified.
// Message area is padded with blank columns after printing.
{
static enum { S_LOAD, S_SHOW, S_SPACE } state;
static uint8_t curLen, showLen;
static uint8_t cBuf[EYE_COL_SIZE];
if (bInit)
{
PRINT("\nText: ", _pText);
_timeLastAnimation = millis();
_M->clear(_sd, _sd + 1);
state = S_LOAD;
}
// Is it time to scroll the text?
if (millis() - _timeLastAnimation < FRAME_TIME/2)
return;
_M->control(MD_MAX72XX::UPDATE, MD_MAX72XX::OFF);
// Now scroll the text
_M->transform(_sd, _sd+1, MD_MAX72XX::TSL); // scroll along by one place
_timeLastAnimation = millis(); // starting time for next scroll
// Now work out what's next using finite state machine to control what we do
switch (state)
{
case S_LOAD: // Load the next character from the font table
// if we reached end of message or empty string, reset the message pointer
if (*_pText == '\0')
{
_pText = nullptr;
break;
}
// otherwise load the character
showLen = _M->getChar(*_pText++, ARRAY_SIZE(cBuf), cBuf);
curLen = 0;
state = S_SHOW;
// fall through to the next state
case S_SHOW: // display the next part of the character
_M->setColumn(_sd, 0, cBuf[curLen++]);
if (curLen == showLen)
{
showLen = (*_pText == '\0' ? 2*EYE_COL_SIZE : 1); // either 1 space or pad to the end of the display if finished
curLen = 0;
state = S_SPACE;
}
break;
case S_SPACE: // display inter-character spacing (blank columns)
_M->setColumn(_sd, 0, 0);
curLen++;
if (curLen >= showLen)
state = S_LOAD;
break;
default:
state = S_LOAD;
}
_M->control(MD_MAX72XX::UPDATE, MD_MAX72XX::ON);
}
void MD_RobotEyes::begin(MD_MAX72XX *M, uint8_t moduleStart)
// initialize other stuff after libraries have started
{
#if DEBUG
Serial.begin(57600);
#endif
PRINTS("\n[MD_RobotEyes Debug]");
_M = M;
_sd = moduleStart;
setAnimation(E_NEUTRAL, false);
};
bool MD_RobotEyes::runAnimation(void)
// Animate the eyes
// Return true if there is no animation happening
{
static animFrame_t thisFrame;
switch (_animState)
{
case S_IDLE: // no animation running - wait for a new one or blink if time to do so
if (_pText != nullptr) // there is some text to show
{
PRINTS("\nIDLE: showing text");
showText(true);
_animState = S_TEXT;
break;
}
// otherwise fall through and try for an animation
case S_RESTART: // back to start of current animation
if (_nextEmotion != E_NONE) // check if we have an animation in the queue
{
PRINTS("\nRESRT: showing animation");
_timeLastAnimation = millis();
// set up the next animation
loadSequence(_nextEmotion);
_nextEmotion = E_NONE;
_animState = S_ANIMATE;
}
else if (_autoBlink) // check if we should be blinking
{
if (((millis() - _timeLastAnimation) >= _timeBlinkMinimum) && (random(1000) > 700))
{
PRINTS("\nRESRT: forcing blink");
setAnimation(E_BLINK, true);
_animState = S_RESTART;
}
}
break;
case S_ANIMATE: // process the next frame for this sequence
PRINT("\nPROCESS: Frame:", _animIndex);
loadFrame(&thisFrame);
drawEyes(thisFrame.eyeData[LEFT_EYE_INDEX], thisFrame.eyeData[RIGHT_EYE_INDEX]);
if (_animReverse) _animIndex--; else _animIndex++;
_timeStartPause = millis();
_animState = S_PAUSE;
break;
case S_PAUSE: // pause this frame for the required time
{
if ((millis() - _timeStartPause) < thisFrame.timeFrame)
break;
// check if this is the end of animation
if ((!_animReverse && _animIndex >= _animEntry.size) ||
(_animReverse && _animIndex < 0))
{
PRINTS("\nPAUSE: Animation end")
if (_autoReverse) // set up the same emotion but in reverse
{
PRINTS(" & auto reverse");
_nextEmotion = _animEntry.e;
_animReverse = true; // set this flag for the restart state
_autoReverse = false; // clear the flag for this animation sequence
_animState = S_RESTART;
}
else
_animState = S_IDLE;
}
else
_animState = S_ANIMATE;
}
break;
case S_TEXT: // currently displaying text
{
showText();
if (_pText == nullptr)
_animState = S_IDLE;
}
break;
default: // something is wrong - reset the FSM
_animState = S_IDLE;
break;
}
return(_animState == S_IDLE);
};

View File

@@ -0,0 +1,211 @@
// Implements a class to draw and animate a pair of 'emotive' eyes for a robot
//
#pragma once
#include <MD_MAX72xx.h>
// Misc defines
#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0])) ///< number of elements in an array
#define EYE_COL_SIZE 8 ///< number of columns in one eye
// Module offsets from first module specified
#define LEFT_MODULE_OFFSET 1 ///< offset from the base LED module for the left eye
#define RIGHT_MODULE_OFFSET 0 ///< offset from the base LED module for the right eye
// Array references for eyeData array below
#define LEFT_EYE_INDEX 1 ///< array reference in the eye data for the left eye
#define RIGHT_EYE_INDEX 0 ///< array reference in the eye data for the right eye
// Basic unit of time a frame is displayed
#define FRAME_TIME 100 ///< minimum animation time
/**
* Robot Eyes Class.
* This class manages the displayed of animated eyes using LED matrices using the functions
* provided by the MD_MAX72xx library.
*/
class MD_RobotEyes
{
public:
/**
* Emotions enumerated type.
*
* This enumerated type defines the emotion animations
* available in the class for the eyes display
*/
//
typedef enum
{
E_NONE, ///< placeholder for no emotions, not user selectable
E_NEUTRAL, ///< eyes in neutral position (no animation)
E_BLINK, ///< both eyes blink
E_WINK, ///< one eye blink
E_LOOK_L, ///< both eyes look left
E_LOOK_R, ///< both eyes look right
E_LOOK_U, ///< both eyes look up
E_LOOK_D, ///< both eyes look down
E_ANGRY, ///< eyes look angry (symmetrical)
E_SAD, ///< eyes look sad (symmetrical)
E_EVIL, ///< eyes look evil (symmetrical)
E_EVIL2, ///< eyes look evil (asymmetrical)
E_SQUINT, ///< both eye squint
E_DEAD, ///< eyes indicate dead (different)
E_SCAN_UD, ///< both eyes scanning Up/Down
E_SCAN_LR, ///< both eyes scanning Left/Right
} emotion_t;
/**
* Class Constructor.
*
* Instantiate a new instance of the class.
*/
MD_RobotEyes(void);
/**
* Class Destructor.
*
* Released any allocated memory and does the necessary to clean
* up once the object is no longer required.
*/
~MD_RobotEyes(void) { };
/**
* Initialize the object.
*
* Initialize the object data. This needs to be called during setup() to initialize new
* data for the class that cannot be done during the object creation.
*
* Outside of the class, the MD_MAX72xx library should be initialized and the pointer
* to the MD_MAX72xx object passed to the parameter. Also, as the eyes could be in the
* middle of a string of LED modules, the first 'eye' module can be specified.
*
* /param M pointer to the MD_MAX72xx library object.
* /param moduleStart the first 'eye' LED module. Defaults to 0 if not specified.
*/
void begin(MD_MAX72XX *M, uint8_t moduleStart = 0);
/**
* Set the animation type and parameters.
*
* Set the next animations to the specified. Additionally, set whether the animation should
* auto reverse the action (eg, blink down then back up again) and whether the animation
* should be run in reverse.
*
* Animations are generally symmetric, so only half the animation needs to be specified.
* If an animated expression needs to be held, the animation should be run without auto
* reverse, which holds the animation at the end point, and then later run the animation
* in reverse from the last position to return to the idle state.
*
* \param e the type of emotion to be displayed, one of the emotion_T enumerated values.
* \param r if true, run auto reverse.
* \param b if true, start the animation from the end of the sequence.
*/
inline void setAnimation(emotion_t e, bool r, bool b = false) { _nextEmotion = e; _autoReverse = r; _animReverse = b; };
/**
* Set the blink time.
*
* When no animation is running and AutoBlink is set, the eyes will occasionally blink.
* Set the minimum time period between blinks. A blink will occur a random time after this.
*
* \param t the minimum time between blinking actions in milliseconds.
*/
inline void setBlinkTime(uint16_t t) { _timeBlinkMinimum = t; };
/**
* Set or reset auto blink mode.
*
* When no animation is running and AutoBlink is set, the eyes will occasionally blink.
*
* \param b set auto blink if true, reset auto blink if false.
*/
inline void setAutoBlink(bool b) { _autoBlink = b; };
/**
* Display a text message.
*
* At the end of the current animation, the text will be scrolled across the 'eyes'
* and then the eyes are returned to the neutral expression
*
* \param p a pointer to a char array containing a nul terminated string.
The string must remain in scope while the message is being displayed.
*/
inline bool setText(char *pText) { if (_pText != nullptr) return(false); else _pText = pText; return(true); };
/**
* Animate the display.
*
* This method needs to be invoked as often as possible to ensure smooth animation.
*
* The calling program should monitor the return value for 'true' in order to know when
* the animation has concluded. A 'true' return value means that the animation is complete.
*
* \return bool true if the animation has completed, false otherwise.
*/
bool runAnimation(void);
protected:
// Animations FSM state
typedef enum
{
S_IDLE,
S_RESTART,
S_ANIMATE,
S_PAUSE,
S_TEXT,
} animState_t;
// Define an animation frame
typedef struct animFrame_t
{
uint8_t eyeData[2]; // [LEFT_MODULE_OFFSET] and [RIGHT_MODULE_OFFSET] eye character from font data
uint16_t timeFrame; // time for this frame in milliseconds
};
// Define an entry in the animation sequence lookup table
typedef struct
{
emotion_t e;
animFrame_t *seq;
uint8_t size;
} animTable_t;
// Display parameters
MD_MAX72XX *_M;
uint16_t _sd; // start module for the display
// Animation parameters
uint32_t _timeStartPause;
uint32_t _timeLastAnimation;
uint16_t _timeBlinkMinimum;
animState_t _animState;
bool _autoBlink;
uint16_t _scrollDelay;
// Animation control data
animTable_t _animEntry; // record with animation sequence parameters
int8_t _animIndex; // current index in the animation sequence
bool _animReverse; // true = reverse sequence, false = normal sequence
bool _autoReverse; // true = always play the reverse, false = selected direction only
emotion_t _nextEmotion; // the next emotion to display
char * _pText; // pointer to text data in user code. Not null means there is text to print
// Methods
void loadEye(uint8_t module, uint8_t ch);
void drawEyes(uint8_t L, uint8_t R);
uint8_t loadSequence(emotion_t e); // return the size of the sequence
void loadFrame(animFrame_t* pBuf);
void showText(bool bInit = false);
void dumpSequence(const animFrame_t* pBuf, uint8_t numElements); // debugging routine only
// Static data tables
static const animFrame_t seqBlink[], seqWink[];
static const animFrame_t seqLeft[], seqRight[], seqUp[], seqDown[];
static const animFrame_t seqAngry[], seqSad[], seqEvil[], seqEvil2[];
static const animFrame_t seqSquint[], seqDead[];
static const animFrame_t seqScanUpDown[], seqScanLeftRight[];
// Lookup table to find animation
static const animTable_t lookupTable[];
};

View File

@@ -0,0 +1,406 @@
// EmotiveEye class static variables
#pragma once
#include "MD_RobotEyes.h"
// Sequences for animations
// Note: must add this to the lookupTable below as well so that the animation
// can be found by the animation engine.
const MD_RobotEyes::animFrame_t MD_RobotEyes::seqBlink[] PROGMEM =
{
{ { 0, 0 }, FRAME_TIME/2 },
{ { 1, 1 }, FRAME_TIME/2 },
{ { 2, 2 }, FRAME_TIME/2 },
{ { 3, 3 }, FRAME_TIME/2 },
{ { 4, 4 }, FRAME_TIME/2 },
{ { 5, 5 }, FRAME_TIME },
};
const MD_RobotEyes::animFrame_t MD_RobotEyes::seqWink[] PROGMEM =
{
{ { 0, 0 }, FRAME_TIME/2 },
{ { 1, 0 }, FRAME_TIME/2 },
{ { 2, 0 }, FRAME_TIME/2 },
{ { 3, 0 }, FRAME_TIME/2 },
{ { 4, 0 }, FRAME_TIME/2 },
{ { 5, 0 }, FRAME_TIME * 2 },
};
const MD_RobotEyes::animFrame_t MD_RobotEyes::seqRight[] PROGMEM =
{
{ { 0, 0 }, FRAME_TIME },
{ { 6, 6 }, FRAME_TIME },
{ { 7, 7 }, FRAME_TIME * 5 },
};
const MD_RobotEyes::animFrame_t MD_RobotEyes::seqLeft[] PROGMEM =
{
{ { 0, 0 }, FRAME_TIME },
{ { 8, 8 }, FRAME_TIME },
{ { 9, 9 }, FRAME_TIME * 5 },
};
const MD_RobotEyes::animFrame_t MD_RobotEyes::seqUp[] PROGMEM =
{
{ { 00, 00 }, FRAME_TIME },
{ { 11, 11 }, FRAME_TIME },
{ { 12, 12 }, FRAME_TIME },
{ { 13, 13 }, FRAME_TIME * 5 },
};
const MD_RobotEyes::animFrame_t MD_RobotEyes::seqDown[] PROGMEM =
{
{ { 00, 00 }, FRAME_TIME },
{ { 14, 14 }, FRAME_TIME },
{ { 15, 15 }, FRAME_TIME },
{ { 16, 16 }, FRAME_TIME * 5 },
};
const MD_RobotEyes::animFrame_t MD_RobotEyes::seqAngry[] PROGMEM =
{
{ { 00, 00 }, FRAME_TIME },
{ { 22, 17 }, FRAME_TIME },
{ { 23, 18 }, FRAME_TIME },
{ { 24, 19 }, FRAME_TIME },
{ { 25, 20 }, 2000 },
};
const MD_RobotEyes::animFrame_t MD_RobotEyes::seqSad[] PROGMEM =
{
{ { 00, 00 }, FRAME_TIME },
{ { 32, 27 }, FRAME_TIME },
{ { 33, 28 }, FRAME_TIME },
{ { 34, 29 }, 2000 },
};
const MD_RobotEyes::animFrame_t MD_RobotEyes::seqEvil[] PROGMEM =
{
{ { 00, 00 }, FRAME_TIME },
{ { 39, 37 }, FRAME_TIME },
{ { 40, 38 }, 2000 },
};
const MD_RobotEyes::animFrame_t MD_RobotEyes::seqEvil2[] PROGMEM =
{
{ { 00, 00 }, FRAME_TIME },
{ { 54, 17 }, FRAME_TIME },
{ { 55, 18 }, FRAME_TIME },
{ { 56, 19 }, FRAME_TIME },
{ { 57, 20 }, 2000 },
};
const MD_RobotEyes::animFrame_t MD_RobotEyes::seqSquint[] PROGMEM =
{
{ { 00, 00 }, FRAME_TIME },
{ { 54, 54 }, FRAME_TIME },
{ { 55, 55 }, FRAME_TIME },
{ { 56, 56 }, FRAME_TIME },
{ { 57, 57 }, 2000 },
};
const MD_RobotEyes::animFrame_t MD_RobotEyes::seqDead[] PROGMEM =
{
{ { 52, 52 }, FRAME_TIME * 4 },
{ { 53, 53 }, FRAME_TIME * 4 },
{ { 52, 52 }, FRAME_TIME * 2 },
};
const MD_RobotEyes::animFrame_t MD_RobotEyes::seqScanLeftRight[] PROGMEM =
{
{ { 41, 41 }, FRAME_TIME * 2 },
{ { 42, 42 }, FRAME_TIME },
{ { 43, 43 }, FRAME_TIME },
{ { 44, 44 }, FRAME_TIME },
};
const MD_RobotEyes::animFrame_t MD_RobotEyes::seqScanUpDown[] PROGMEM =
{
{ { 46, 46 }, FRAME_TIME * 2 },
{ { 47, 47 }, FRAME_TIME },
{ { 48, 48 }, FRAME_TIME },
{ { 49, 49 }, FRAME_TIME },
{ { 50, 50 }, FRAME_TIME },
{ { 51, 51 }, FRAME_TIME },
};
// Lookup table to find animation sequences
// Table associates the data for an emotion with the sequence table and it's size
static const MD_RobotEyes::animTable_t MD_RobotEyes::lookupTable[] PROGMEM =
{
{ MD_RobotEyes::E_NEUTRAL, MD_RobotEyes::seqBlink, 1 }, // special case, fixed neutral stare
{ MD_RobotEyes::E_BLINK, MD_RobotEyes::seqBlink, ARRAY_SIZE(MD_RobotEyes::seqBlink) },
{ MD_RobotEyes::E_WINK, MD_RobotEyes::seqWink, ARRAY_SIZE(MD_RobotEyes::seqWink) },
{ MD_RobotEyes::E_LOOK_L, MD_RobotEyes::seqLeft, ARRAY_SIZE(MD_RobotEyes::seqLeft) },
{ MD_RobotEyes::E_LOOK_R, MD_RobotEyes::seqRight, ARRAY_SIZE(MD_RobotEyes::seqRight) },
{ MD_RobotEyes::E_LOOK_U, MD_RobotEyes::seqUp, ARRAY_SIZE(MD_RobotEyes::seqUp) },
{ MD_RobotEyes::E_LOOK_D, MD_RobotEyes::seqDown, ARRAY_SIZE(MD_RobotEyes::seqDown) },
{ MD_RobotEyes::E_ANGRY, MD_RobotEyes::seqAngry, ARRAY_SIZE(MD_RobotEyes::seqAngry) },
{ MD_RobotEyes::E_SAD, MD_RobotEyes::seqSad, ARRAY_SIZE(MD_RobotEyes::seqSad) },
{ MD_RobotEyes::E_EVIL, MD_RobotEyes::seqEvil, ARRAY_SIZE(MD_RobotEyes::seqEvil) },
{ MD_RobotEyes::E_EVIL2, MD_RobotEyes::seqEvil2, ARRAY_SIZE(MD_RobotEyes::seqEvil2) },
{ MD_RobotEyes::E_SQUINT, MD_RobotEyes::seqSquint, ARRAY_SIZE(MD_RobotEyes::seqSquint) },
{ MD_RobotEyes::E_DEAD, MD_RobotEyes::seqDead, ARRAY_SIZE(MD_RobotEyes::seqDead) },
{ MD_RobotEyes::E_SCAN_LR, MD_RobotEyes::seqScanLeftRight, ARRAY_SIZE(MD_RobotEyes::seqScanLeftRight) },
{ MD_RobotEyes::E_SCAN_UD, MD_RobotEyes::seqScanUpDown, ARRAY_SIZE(MD_RobotEyes::seqScanUpDown) },
};
// Font file (bitmaps for emotion animation frames)
MD_MAX72XX::fontType_t _RobotEyes_Font[] PROGMEM =
{
8, 0, 126, 129, 177, 177, 129, 126, 0, // 0 - 'Rest Position'
8, 0, 124, 130, 178, 178, 130, 124, 0, // 1 - 'Blink 1'
8, 0, 120, 132, 180, 180, 132, 120, 0, // 2 - 'Blink 2'
8, 0, 48, 72, 120, 120, 72, 48, 0, // 3 - 'Blink 3'
8, 0, 32, 80, 112, 112, 80, 32, 0, // 4 - 'Blink 4'
8, 0, 32, 96, 96, 96, 96, 32, 0, // 5 - 'Blink 5'
8, 0, 126, 129, 129, 177, 177, 126, 0, // 6 - 'Right 1'
8, 0, 0, 126, 129, 129, 177, 177, 126, // 7 - 'Right 2'
8, 0, 126, 177, 177, 129, 129, 126, 0, // 8 - 'Left 1'
8, 126, 177, 177, 129, 129, 126, 0, 0, // 9 - 'Left 2'
0, // 10
8, 0, 126, 129, 153, 153, 129, 126, 0, // 11 - 'Up 1'
8, 0, 126, 129, 141, 141, 129, 126, 0, // 12 - 'Up 2'
8, 0, 126, 129, 135, 135, 129, 126, 0, // 13 - 'Up 3'
8, 0, 126, 129, 225, 225, 129, 126, 0, // 14 - 'Down 1'
8, 0, 126, 129, 193, 193, 129, 126, 0, // 15 - 'Down 2'
8, 0, 124, 130, 194, 194, 130, 124, 0, // 16 - 'Down 3'
8, 0, 124, 130, 177, 177, 129, 126, 0, // 17 - 'Angry L 1'
8, 0, 120, 132, 178, 177, 129, 126, 0, // 18 - 'Angry L 2'
8, 0, 112, 136, 164, 178, 129, 126, 0, // 19 - 'Angry L 3'
8, 0, 96, 144, 168, 180, 130, 127, 0, // 20 - 'Angry L 4'
0, // 21
8, 0, 126, 129, 177, 177, 130, 124, 0, // 22 - 'Angry R 1'
8, 0, 126, 129, 177, 178, 132, 120, 0, // 23 - 'Angry R 2'
8, 0, 126, 129, 178, 164, 136, 112, 0, // 24 - 'Angry R 3'
8, 0, 127, 130, 180, 168, 144, 96, 0, // 25 - 'Angry R 4'
0, // 26
8, 0, 62, 65, 153, 153, 130, 124, 0, // 27 - 'Sad L 1'
8, 0, 30, 33, 89, 154, 132, 120, 0, // 28 - 'Sad L 2'
8, 0, 14, 17, 41, 90, 132, 120, 0, // 29 - 'Sad L 3'
0, // 30
0, // 31
8, 0, 124, 130, 153, 153, 65, 62, 0, // 32 - 'Sad R 1'
8, 0, 120, 132, 154, 89, 33, 30, 0, // 33 - 'Sad R 2'
8, 0, 120, 132, 90, 41, 17, 14, 0, // 34 - 'Sad R 3'
0, // 35
0, // 36
8, 0, 124, 194, 177, 177, 193, 126, 0, // 37 - 'Evil L 1'
8, 0, 56, 68, 178, 177, 66, 60, 0, // 38 - 'Evil L 2'
8, 0, 126, 193, 177, 177, 194, 124, 0, // 39 - 'Evil R 1'
8, 0, 60, 66, 177, 178, 68, 56, 0, // 40 - 'Evil R 2'
8, 0, 126, 129, 129, 129, 189, 126, 0, // 41 - 'Scan H 1'
8, 0, 126, 129, 129, 189, 129, 126, 0, // 42 - 'Scan H 2'
8, 0, 126, 129, 189, 129, 129, 126, 0, // 43 - 'Scan H 3'
8, 0, 126, 189, 129, 129, 129, 126, 0, // 44 - 'Scan H 4'
0, // 45
8, 0, 126, 129, 131, 131, 129, 126, 0, // 46 - 'Scan V 1'
8, 0, 126, 129, 133, 133, 129, 126, 0, // 47 - 'Scan V 2'
8, 0, 126, 129, 137, 137, 129, 126, 0, // 48 - 'Scan V 3'
8, 0, 126, 129, 145, 145, 129, 126, 0, // 49 - 'Scan V 4'
8, 0, 126, 129, 161, 161, 129, 126, 0, // 50 - 'Scan V 5'
8, 0, 126, 129, 193, 193, 129, 126, 0, // 51 - 'Scan V 6'
8, 0, 126, 137, 157, 137, 129, 126, 0, // 52 - 'RIP 1'
8, 0, 126, 129, 145, 185, 145, 126, 0, // 53 - 'RIP 2'
8, 0, 60, 66, 114, 114, 66, 60, 0, // 54 - 'Peering 1'
8, 0, 56, 68, 116, 116, 68, 56, 0, // 55 - 'Peering 2'
8, 0, 48, 72, 120, 120, 72, 48, 0, // 56 - 'Peering 3'
8, 0, 32, 80, 112, 112, 80, 32, 0, // 57 - 'Peering 4'
0, // 58
0, // 59 - 'Unused'
0, // 60 - 'Unused'
0, // 61 - 'Unused'
0, // 62 - 'Unused'
0, // 63 - 'Unused'
0, // 64 - 'Unused'
0, // 65 - 'Unused'
0, // 66 - 'Unused'
0, // 67 - 'Unused'
0, // 68 - 'Unused'
0, // 69 - 'Unused'
0, // 70 - 'Unused'
0, // 71 - 'Unused'
0, // 72 - 'Unused'
0, // 73 - 'Unused'
0, // 74 - 'Unused'
0, // 75 - 'Unused'
0, // 76 - 'Unused'
0, // 77 - 'Unused'
0, // 78 - 'Unused'
0, // 79 - 'Unused'
0, // 80 - 'Unused'
0, // 81 - 'Unused'
0, // 82 - 'Unused'
0, // 83 - 'Unused'
0, // 84 - 'Unused'
0, // 85 - 'Unused'
0, // 86 - 'Unused'
0, // 87 - 'Unused'
0, // 88 - 'Unused'
0, // 89 - 'Unused'
0, // 90 - 'Unused'
0, // 91 - 'Unused'
0, // 92 - 'Unused'
0, // 93 - 'Unused'
0, // 94 - 'Unused'
0, // 95 - 'Unused'
0, // 96 - 'Unused'
0, // 97 - 'Unused'
0, // 98 - 'Unused'
0, // 99 - 'Unused'
0, // 100 - 'Unused'
0, // 101 - 'Unused'
0, // 102 - 'Unused'
0, // 103 - 'Unused'
0, // 104 - 'Unused'
0, // 105 - 'Unused'
0, // 106 - 'Unused'
0, // 107 - 'Unused'
0, // 108 - 'Unused'
0, // 109 - 'Unused'
0, // 110 - 'Unused'
0, // 111 - 'Unused'
0, // 112 - 'Unused'
0, // 113 - 'Unused'
0, // 114 - 'Unused'
0, // 115 - 'Unused'
0, // 116 - 'Unused'
0, // 117 - 'Unused'
0, // 118 - 'Unused'
0, // 119 - 'Unused'
0, // 120 - 'Unused'
0, // 121 - 'Unused'
0, // 122 - 'Unused'
0, // 123 - 'Unused'
0, // 124 - 'Unused'
0, // 125 - 'Unused'
0, // 126 - 'Unused'
0, // 127 - 'Unused'
0, // 128 - 'Unused'
0, // 129 - 'Unused'
0, // 130 - 'Unused'
0, // 131 - 'Unused'
0, // 132 - 'Unused'
0, // 133 - 'Unused'
0, // 134 - 'Unused'
0, // 135 - 'Unused'
0, // 136 - 'Unused'
0, // 137 - 'Unused'
0, // 138 - 'Unused'
0, // 139 - 'Unused'
0, // 140 - 'Unused'
0, // 141 - 'Unused'
0, // 142 - 'Unused'
0, // 143 - 'Unused'
0, // 144 - 'Unused'
0, // 145 - 'Unused'
0, // 146 - 'Unused'
0, // 147 - 'Unused'
0, // 148 - 'Unused'
0, // 149 - 'Unused'
0, // 150 - 'Unused'
0, // 151 - 'Unused'
0, // 152 - 'Unused'
0, // 153 - 'Unused'
0, // 154 - 'Unused'
0, // 155 - 'Unused'
0, // 156 - 'Unused'
0, // 157 - 'Unused'
0, // 158 - 'Unused'
0, // 159 - 'Unused'
0, // 160 - 'Unused'
0, // 161 - 'Unused'
0, // 162 - 'Unused'
0, // 163 - 'Unused'
0, // 164 - 'Unused'
0, // 165 - 'Unused'
0, // 166 - 'Unused'
0, // 167 - 'Unused'
0, // 168 - 'Unused'
0, // 169 - 'Unused'
0, // 170 - 'Unused'
0, // 171 - 'Unused'
0, // 172 - 'Unused'
0, // 173 - 'Unused'
0, // 174 - 'Unused'
0, // 175 - 'Unused'
0, // 176 - 'Unused'
0, // 177 - 'Unused'
0, // 178 - 'Unused'
0, // 179 - 'Unused'
0, // 180 - 'Unused'
0, // 181 - 'Unused'
0, // 182 - 'Unused'
0, // 183 - 'Unused'
0, // 184 - 'Unused'
0, // 185 - 'Unused'
0, // 186 - 'Unused'
0, // 187 - 'Unused'
0, // 188 - 'Unused'
0, // 189 - 'Unused'
0, // 190 - 'Unused'
0, // 191 - 'Unused'
0, // 192 - 'Unused'
0, // 193 - 'Unused'
0, // 194 - 'Unused'
0, // 195 - 'Unused'
0, // 196 - 'Unused'
0, // 197 - 'Unused'
0, // 198 - 'Unused'
0, // 199 - 'Unused'
0, // 200 - 'Unused'
0, // 201 - 'Unused'
0, // 202 - 'Unused'
0, // 203 - 'Unused'
0, // 204 - 'Unused'
0, // 205 - 'Unused'
0, // 206 - 'Unused'
0, // 207 - 'Unused'
0, // 208 - 'Unused'
0, // 209 - 'Unused'
0, // 210 - 'Unused'
0, // 211 - 'Unused'
0, // 212 - 'Unused'
0, // 213 - 'Unused'
0, // 214 - 'Unused'
0, // 215 - 'Unused'
0, // 216 - 'Unused'
0, // 217 - 'Unused'
0, // 218 - 'Unused'
0, // 219 - 'Unused'
0, // 220 - 'Unused'
0, // 221 - 'Unused'
0, // 222 - 'Unused'
0, // 223 - 'Unused'
0, // 224 - 'Unused'
0, // 225 - 'Unused'
0, // 226 - 'Unused'
0, // 227 - 'Unused'
0, // 228 - 'Unused'
0, // 229 - 'Unused'
0, // 230 - 'Unused'
0, // 231 - 'Unused'
0, // 232 - 'Unused'
0, // 233 - 'Unused'
0, // 234 - 'Unused'
0, // 235 - 'Unused'
0, // 236 - 'Unused'
0, // 237 - 'Unused'
0, // 238 - 'Unused'
0, // 239 - 'Unused'
0, // 240 - 'Unused'
0, // 241 - 'Unused'
0, // 242 - 'Unused'
0, // 243 - 'Unused'
0, // 244 - 'Unused'
0, // 245 - 'Unused'
0, // 246 - 'Unused'
0, // 247 - 'Unused'
0, // 248 - 'Unused'
0, // 249 - 'Unused'
0, // 250 - 'Unused'
0, // 251 - 'Unused'
0, // 252 - 'Unused'
0, // 253 - 'Unused'
0, // 254 - 'Unused'
0, // 255 - 'Unused'
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,159 @@
// Use the MD_MAX72XX library to Display a Scrolling Chart
//
// Scroll Chart Style can be changed from line to bar chart, triggered
// by a switch on the MODE_SWITCH pin.
//
// Uses the MD_UIswitch library found at https://github.com/MajicDesigns/MD_UISwitch
#include <MD_MAX72xx.h>
#include <SPI.h>
#include <MD_UISwitch.h>
#define DEBUG 0 // Enable or disable (default) debugging output
#if DEBUG
#define PRINT(s, v) { Serial.print(F(s)); Serial.print(v); } // Print a string followed by a value (decimal)
#define PRINTX(s, v) { Serial.print(F(s)); Serial.print(v, HEX); } // Print a string followed by a value (hex)
#define PRINTB(s, v) { Serial.print(F(s)); Serial.print(v, BIN); } // Print a string followed by a value (binary)
#define PRINTC(s, v) { Serial.print(F(s)); Serial.print((char)v); } // Print a string followed by a value (char)
#define PRINTS(s) { Serial.print(F(s)); } // Print a string
#else
#define PRINT(s, v) // Print a string followed by a value (decimal)
#define PRINTX(s, v) // Print a string followed by a value (hex)
#define PRINTB(s, v) // Print a string followed by a value (binary)
#define PRINTC(s, v) // Print a string followed by a value (char)
#define PRINTS(s) // Print a string
#endif
// --------------------
// MD_MAX72xx hardware definitions and object
// 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 // 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); // SPI hardware interface
//MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES); // Arbitrary pins
// --------------------
// Mode keyswitch parameters and object
//
#define MODE_SWITCH 9 // Digital Pin
MD_UISwitch_Digital ks = MD_UISwitch_Digital(MODE_SWITCH, LOW);
// --------------------
// Constant parameters
//
// Various delays in milliseconds
#define Next_POINT_DELAY 40
// ========== General Variables ===========
//
uint32_t prevTime = 0; // Used for remembering the mills() value
// ========== Graphic routines ===========
//
bool graphDisplay(bool bInit, uint8_t nType)
{
static int8_t curPoint = 0;
uint8_t curCol = 0;
// are we initializing?
if (bInit)
{
resetDisplay();
curPoint = 4;
bInit = false;
}
else if (millis() - prevTime >= Next_POINT_DELAY)
{
prevTime = millis(); // rest for next time
// work out the new value for the height depending on the chart type
switch (nType)
{
case 0: // continuous display next point should be +/-1 or 0
curPoint += random(3) - 1;
if (curPoint < 0) curPoint = 0;
if (curPoint > 7) curPoint = 7;
break;
case 1: // random height
case 2:
curPoint = random(8);
break;
}
// now work out the new column value
switch (nType)
{
case 0: // just a dot
case 1:
curCol = (1 << curPoint);
break;
case 2: // bar chart
for (uint8_t i=0; i<8; i++)
curCol |= (i<curPoint ? 0 : 1) << i;
break;
}
// Shift over and insert the new column
mx.transform(MD_MAX72XX::TSL);
mx.setColumn(0, curCol);
}
return(bInit);
}
// ========== Control routines ===========
//
void resetDisplay(void)
{
mx.control(MD_MAX72XX::INTENSITY, MAX_INTENSITY/2);
mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::ON);
mx.clear();
prevTime = 0;
}
void runAnimation(void)
// Schedule the animations, switching to the next one when the
// the mode switch is pressed.
{
static uint8_t state = 0;
static bool bRestart = true;
// check if the switch is pressed and handle that first
if (ks.read() == MD_UISwitch::KEY_PRESS)
{
state = (state + 1) % 3;
bRestart = true;
};
// now do whatever we do in the current state
bRestart = graphDisplay(bRestart, state);
}
void setup()
{
mx.begin();
ks.begin();
#if DEBUG
Serial.begin(57600);
#endif
PRINTS("\n[MD_MAX72XX Scroll Chart]");
}
void loop()
{
runAnimation();
}

View File

@@ -0,0 +1,124 @@
// Program to exercise the MD_MAX72XX library
//
// Test the library transformation functions
#include <MD_MAX72xx.h>
#include <SPI.h>
// Use a button to transfer between transformations or just do it on a timer basis
#define USE_SWITCH_INPUT 1
#define SWITCH_PIN 8 // switch pin if enabled - active LOW
// We always wait a bit between updates of the display
#define DELAYTIME 500 // in milliseconds
// Number of times to repeat the transformation animations
#define REPEATS_PRESET 16
// 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 4
#define WRAPAROUND_MODE MD_MAX72XX::ON
#define CLK_PIN 13 // or SCK
#define DATA_PIN 11 // or MOSI
#define CS_PIN 10 // or SS
// SPI hardware interface
MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
// Arbitrary pins
//MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES);
bool changeState(void)
{
bool b = false;
#if USE_SWITCH_INPUT
static int8_t lastStatus = HIGH;
int8_t status = digitalRead(SWITCH_PIN);
b = (lastStatus == HIGH) && (status == LOW);
lastStatus = status;
#else
static uint32_t lastTime = 0;
static uint8_t repeatCount = 0;
if (repeatCount == 0)
repeatCount = REPEATS_PRESET;
if (millis()-lastTime >= DELAYTIME)
{
lastTime = millis();
b = (--repeatCount == 0);
}
#endif
return(b);
}
void transformDemo(MD_MAX72XX::transformType_t tt, bool bNew)
{
static uint32_t lastTime = 0;
if (bNew)
{
mx.clear();
for (uint8_t i=0; i<MAX_DEVICES; i++)
mx.setChar(((i+1)*COL_SIZE)-1, 'o'+i);
lastTime = millis();
}
if (millis() - lastTime >= DELAYTIME)
{
mx.transform(0, MAX_DEVICES-1, tt);
lastTime = millis();
}
}
void setup()
{
mx.begin();
// use wraparound mode
mx.control(MD_MAX72XX::WRAPAROUND, WRAPAROUND_MODE);
#if USE_SWITCH_INPUT
pinMode(SWITCH_PIN, INPUT_PULLUP);
#endif
Serial.begin(57600);
Serial.println("[Transform Test]");
}
void loop()
{
static int8_t tState = -1;
static bool bNew = true;
if (bNew)
{
tState = (tState+1) % 8;
Serial.print("State: "); Serial.println(tState);
}
switch (tState)
{
case 0: transformDemo(MD_MAX72XX::TSL, bNew); break;
case 1: transformDemo(MD_MAX72XX::TSR, bNew); break;
case 2: transformDemo(MD_MAX72XX::TSU, bNew); break;
case 3: transformDemo(MD_MAX72XX::TSD, bNew); break;
case 4: transformDemo(MD_MAX72XX::TFUD, bNew); break;
case 5: transformDemo(MD_MAX72XX::TFLR, bNew); break;
case 6: transformDemo(MD_MAX72XX::TRC, bNew); break;
case 7: transformDemo(MD_MAX72XX::TINV, bNew); break;
default: tState = 0; // just in case
}
bNew = changeState();
}

View File

@@ -0,0 +1,628 @@
// Program to exercise the MD_MAX72XX library
//
// Uses most of the functions in the library
#include <MD_MAX72xx.h>
//#include <SPI.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 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 11
#define CLK_PIN 13 // or SCK
#define DATA_PIN 11 // or MOSI
#define CS_PIN 10 // or SS
// SPI hardware interface
MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
// Arbitrary pins
// MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES);
// We always wait a bit between updates of the display
#define DELAYTIME 100 // in milliseconds
void scrollText(char *p)
{
uint8_t charWidth;
uint8_t cBuf[8]; // this should be ok for all built-in fonts
PRINTS("\nScrolling text");
mx.clear();
while (*p != '\0')
{
charWidth = mx.getChar(*p++, sizeof(cBuf) / sizeof(cBuf[0]), cBuf);
for (uint8_t i=0; i<=charWidth; i++) // allow space between characters
{
mx.transform(MD_MAX72XX::TSL);
if (i < charWidth)
mx.setColumn(0, cBuf[i]);
delay(DELAYTIME);
}
}
}
void zeroPointSet()
// Demonstrates the use of setPoint and
// show where the zero point is in the display
{
PRINTS("\nZero point highlight");
mx.clear();
if (MAX_DEVICES > 1)
mx.setChar((2*COL_SIZE)-1, '0');
for (uint8_t i=0; i<ROW_SIZE; i++)
{
mx.setPoint(i, i, true);
mx.setPoint(0, i, true);
mx.setPoint(i, 0, true);
delay(DELAYTIME);
}
delay(DELAYTIME*3);
}
void rows()
// Demonstrates the use of setRow()
{
PRINTS("\nRows 0->7");
mx.clear();
for (uint8_t row=0; row<ROW_SIZE; row++)
{
mx.setRow(row, 0xff);
delay(2*DELAYTIME);
mx.setRow(row, 0x00);
}
}
void checkboard()
// nested rectangles spanning the entire display
{
uint8_t chkCols[][2] = { { 0x55, 0xaa }, { 0x33, 0xcc }, { 0x0f, 0xf0 }, { 0xff, 0x00 } };
PRINTS("\nCheckboard");
mx.clear();
for (uint8_t pattern = 0; pattern < sizeof(chkCols)/sizeof(chkCols[0]); pattern++)
{
uint8_t col = 0;
uint8_t idx = 0;
uint8_t rep = 1 << pattern;
while (col < mx.getColumnCount())
{
for (uint8_t r = 0; r < rep; r++)
mx.setColumn(col++, chkCols[pattern][idx]); // use odd/even column masks
idx++;
if (idx > 1) idx = 0;
}
delay(10 * DELAYTIME);
}
}
void columns()
// Demonstrates the use of setColumn()
{
PRINTS("\nCols 0->max");
mx.clear();
for (uint8_t col=0; col<mx.getColumnCount(); col++)
{
mx.setColumn(col, 0xff);
delay(DELAYTIME/MAX_DEVICES);
mx.setColumn(col, 0x00);
}
}
void cross()
// Combination of setRow() and setColumn() with user controlled
// display updates to ensure concurrent changes.
{
PRINTS("\nMoving cross");
mx.clear();
mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::OFF);
// diagonally down the display R to L
for (uint8_t i=0; i<ROW_SIZE; i++)
{
for (uint8_t j=0; j<MAX_DEVICES; j++)
{
mx.setColumn(j, i, 0xff);
mx.setRow(j, i, 0xff);
}
mx.update();
delay(DELAYTIME);
for (uint8_t j=0; j<MAX_DEVICES; j++)
{
mx.setColumn(j, i, 0x00);
mx.setRow(j, i, 0x00);
}
}
// moving up the display on the R
for (int8_t i=ROW_SIZE-1; i>=0; i--)
{
for (uint8_t j=0; j<MAX_DEVICES; j++)
{
mx.setColumn(j, i, 0xff);
mx.setRow(j, ROW_SIZE-1, 0xff);
}
mx.update();
delay(DELAYTIME);
for (uint8_t j=0; j<MAX_DEVICES; j++)
{
mx.setColumn(j, i, 0x00);
mx.setRow(j, ROW_SIZE-1, 0x00);
}
}
// diagonally up the display L to R
for (uint8_t i=0; i<ROW_SIZE; i++)
{
for (uint8_t j=0; j<MAX_DEVICES; j++)
{
mx.setColumn(j, i, 0xff);
mx.setRow(j, ROW_SIZE-1-i, 0xff);
}
mx.update();
delay(DELAYTIME);
for (uint8_t j=0; j<MAX_DEVICES; j++)
{
mx.setColumn(j, i, 0x00);
mx.setRow(j, ROW_SIZE-1-i, 0x00);
}
}
mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::ON);
}
void bullseye()
// Demonstrate the use of buffer based repeated patterns
// across all devices.
{
PRINTS("\nBullseye");
mx.clear();
mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::OFF);
for (uint8_t n=0; n<3; n++)
{
byte b = 0xff;
int i = 0;
while (b != 0x00)
{
for (uint8_t j=0; j<MAX_DEVICES+1; j++)
{
mx.setRow(j, i, b);
mx.setColumn(j, i, b);
mx.setRow(j, ROW_SIZE-1-i, b);
mx.setColumn(j, COL_SIZE-1-i, b);
}
mx.update();
delay(3*DELAYTIME);
for (uint8_t j=0; j<MAX_DEVICES+1; j++)
{
mx.setRow(j, i, 0);
mx.setColumn(j, i, 0);
mx.setRow(j, ROW_SIZE-1-i, 0);
mx.setColumn(j, COL_SIZE-1-i, 0);
}
bitClear(b, i);
bitClear(b, 7-i);
i++;
}
while (b != 0xff)
{
for (uint8_t j=0; j<MAX_DEVICES+1; j++)
{
mx.setRow(j, i, b);
mx.setColumn(j, i, b);
mx.setRow(j, ROW_SIZE-1-i, b);
mx.setColumn(j, COL_SIZE-1-i, b);
}
mx.update();
delay(3*DELAYTIME);
for (uint8_t j=0; j<MAX_DEVICES+1; j++)
{
mx.setRow(j, i, 0);
mx.setColumn(j, i, 0);
mx.setRow(j, ROW_SIZE-1-i, 0);
mx.setColumn(j, COL_SIZE-1-i, 0);
}
i--;
bitSet(b, i);
bitSet(b, 7-i);
}
}
mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::ON);
}
void stripe()
// Demonstrates animation of a diagonal stripe moving across the display
// with points plotted outside the display region ignored.
{
const uint16_t maxCol = MAX_DEVICES*ROW_SIZE;
const uint8_t stripeWidth = 10;
PRINTS("\nEach individually by row then col");
mx.clear();
for (uint16_t col=0; col<maxCol + ROW_SIZE + stripeWidth; col++)
{
for (uint8_t row=0; row < ROW_SIZE; row++)
{
mx.setPoint(row, col-row, true);
mx.setPoint(row, col-row - stripeWidth, false);
}
delay(DELAYTIME);
}
}
void spiral()
// setPoint() used to draw a spiral across the whole display
{
PRINTS("\nSpiral in");
int rmin = 0, rmax = ROW_SIZE-1;
int cmin = 0, cmax = (COL_SIZE*MAX_DEVICES)-1;
mx.clear();
while ((rmax > rmin) && (cmax > cmin))
{
// do row
for (int i=cmin; i<=cmax; i++)
{
mx.setPoint(rmin, i, true);
delay(DELAYTIME/MAX_DEVICES);
}
rmin++;
// do column
for (uint8_t i=rmin; i<=rmax; i++)
{
mx.setPoint(i, cmax, true);
delay(DELAYTIME/MAX_DEVICES);
}
cmax--;
// do row
for (int i=cmax; i>=cmin; i--)
{
mx.setPoint(rmax, i, true);
delay(DELAYTIME/MAX_DEVICES);
}
rmax--;
// do column
for (uint8_t i=rmax; i>=rmin; i--)
{
mx.setPoint(i, cmin, true);
delay(DELAYTIME/MAX_DEVICES);
}
cmin++;
}
}
void bounce()
// Animation of a bouncing ball
{
const int minC = 0;
const int maxC = mx.getColumnCount()-1;
const int minR = 0;
const int maxR = ROW_SIZE-1;
int nCounter = 0;
int r = 0, c = 2;
int8_t dR = 1, dC = 1; // delta row and column
PRINTS("\nBouncing ball");
mx.clear();
while (nCounter++ < 200)
{
mx.setPoint(r, c, false);
r += dR;
c += dC;
mx.setPoint(r, c, true);
delay(DELAYTIME/2);
if ((r == minR) || (r == maxR))
dR = -dR;
if ((c == minC) || (c == maxC))
dC = -dC;
}
}
void intensity()
// Demonstrates the control of display intensity (brightness) across
// the full range.
{
uint8_t row;
PRINTS("\nVary intensity ");
mx.clear();
// Grow and get brighter
row = 0;
for (int8_t i=0; i<=MAX_INTENSITY; i++)
{
mx.control(MD_MAX72XX::INTENSITY, i);
if (i%2 == 0)
mx.setRow(row++, 0xff);
delay(DELAYTIME*3);
}
mx.control(MD_MAX72XX::INTENSITY, 8);
}
void blinking()
// Uses the test function of the MAX72xx to blink the display on and off.
{
int nDelay = 1000;
PRINTS("\nBlinking");
mx.clear();
while (nDelay > 0)
{
mx.control(MD_MAX72XX::TEST, MD_MAX72XX::ON);
delay(nDelay);
mx.control(MD_MAX72XX::TEST, MD_MAX72XX::OFF);
delay(nDelay);
nDelay -= DELAYTIME;
}
}
void scanLimit(void)
// Uses scan limit function to restrict the number of rows displayed.
{
PRINTS("\nScan Limit");
mx.clear();
mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::OFF);
for (uint8_t row=0; row<ROW_SIZE; row++)
mx.setRow(row, 0xff);
mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::ON);
for (int8_t s=MAX_SCANLIMIT; s>=0; s--)
{
mx.control(MD_MAX72XX::SCANLIMIT, s);
delay(DELAYTIME*5);
}
mx.control(MD_MAX72XX::SCANLIMIT, MAX_SCANLIMIT);
}
void transformation1()
// Demonstrates the use of transform() to move bitmaps on the display
// In this case a user defined bitmap is created and animated.
{
uint8_t arrow[COL_SIZE] =
{
0b00001000,
0b00011100,
0b00111110,
0b01111111,
0b00011100,
0b00011100,
0b00111110,
0b00000000
};
MD_MAX72XX::transformType_t t[] =
{
MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL,
MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL,
MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL,
MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL,
MD_MAX72XX::TFLR,
MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR,
MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR,
MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR,
MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR,
MD_MAX72XX::TRC,
MD_MAX72XX::TSD, MD_MAX72XX::TSD, MD_MAX72XX::TSD, MD_MAX72XX::TSD,
MD_MAX72XX::TSD, MD_MAX72XX::TSD, MD_MAX72XX::TSD, MD_MAX72XX::TSD,
MD_MAX72XX::TFUD,
MD_MAX72XX::TSU, MD_MAX72XX::TSU, MD_MAX72XX::TSU, MD_MAX72XX::TSU,
MD_MAX72XX::TSU, MD_MAX72XX::TSU, MD_MAX72XX::TSU, MD_MAX72XX::TSU,
MD_MAX72XX::TINV,
MD_MAX72XX::TRC, MD_MAX72XX::TRC, MD_MAX72XX::TRC, MD_MAX72XX::TRC,
MD_MAX72XX::TINV
};
PRINTS("\nTransformation1");
mx.clear();
// use the arrow bitmap
mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::OFF);
for (uint8_t j=0; j<mx.getDeviceCount(); j++)
mx.setBuffer(((j+1)*COL_SIZE)-1, COL_SIZE, arrow);
mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::ON);
delay(DELAYTIME);
// run through the transformations
mx.control(MD_MAX72XX::WRAPAROUND, MD_MAX72XX::ON);
for (uint8_t i=0; i<(sizeof(t)/sizeof(t[0])); i++)
{
mx.transform(t[i]);
delay(DELAYTIME*4);
}
mx.control(MD_MAX72XX::WRAPAROUND, MD_MAX72XX::OFF);
}
void transformation2()
// Demonstrates the use of transform() to move bitmaps on the display
// In this case font characters are loaded into the display for animation.
{
MD_MAX72XX::transformType_t t[] =
{
MD_MAX72XX::TINV,
MD_MAX72XX::TRC, MD_MAX72XX::TRC, MD_MAX72XX::TRC, MD_MAX72XX::TRC,
MD_MAX72XX::TINV,
MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL,
MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR,
MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR,
MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL, MD_MAX72XX::TSL,
MD_MAX72XX::TSR, MD_MAX72XX::TSR, MD_MAX72XX::TSR,
MD_MAX72XX::TSD, MD_MAX72XX::TSU, MD_MAX72XX::TSD, MD_MAX72XX::TSU,
MD_MAX72XX::TFLR, MD_MAX72XX::TFLR, MD_MAX72XX::TFUD, MD_MAX72XX::TFUD
};
PRINTS("\nTransformation2");
mx.clear();
mx.control(MD_MAX72XX::WRAPAROUND, MD_MAX72XX::OFF);
// draw something that will show changes
for (uint8_t j=0; j<mx.getDeviceCount(); j++)
{
mx.setChar(((j+1)*COL_SIZE)-1, '0'+j);
}
delay(DELAYTIME*5);
// run thru transformations
for (uint8_t i=0; i<(sizeof(t)/sizeof(t[0])); i++)
{
mx.transform(t[i]);
delay(DELAYTIME*3);
}
}
void wrapText()
// Display text and animate scrolling using auto wraparound of the buffer
{
PRINTS("\nwrapText");
mx.clear();
mx.wraparound(MD_MAX72XX::ON);
// draw something that will show changes
for (uint16_t j=0; j<mx.getDeviceCount(); j++)
{
mx.setChar(((j+1)*COL_SIZE)-1, (j&1 ? 'M' : 'W'));
}
delay(DELAYTIME*5);
// run thru transformations
for (uint16_t i=0; i<3*COL_SIZE*MAX_DEVICES; i++)
{
mx.transform(MD_MAX72XX::TSL);
delay(DELAYTIME/2);
}
for (uint16_t i=0; i<3*COL_SIZE*MAX_DEVICES; i++)
{
mx.transform(MD_MAX72XX::TSR);
delay(DELAYTIME/2);
}
for (uint8_t i=0; i<ROW_SIZE; i++)
{
mx.transform(MD_MAX72XX::TSU);
delay(DELAYTIME*2);
}
for (uint8_t i=0; i<ROW_SIZE; i++)
{
mx.transform(MD_MAX72XX::TSD);
delay(DELAYTIME*2);
}
mx.wraparound(MD_MAX72XX::OFF);
}
void showCharset(void)
// Run through display of the the entire font characters set
{
mx.clear();
mx.update(MD_MAX72XX::OFF);
for (uint16_t i=0; i<256; i++)
{
mx.clear(0);
mx.setChar(COL_SIZE-1, i);
if (MAX_DEVICES >= 3)
{
char hex[3];
sprintf(hex, "%02X", i);
mx.clear(1);
mx.setChar((2*COL_SIZE)-1,hex[1]);
mx.clear(2);
mx.setChar((3*COL_SIZE)-1,hex[0]);
}
mx.update();
delay(DELAYTIME*2);
}
mx.update(MD_MAX72XX::ON);
}
void setup()
{
mx.begin();
#if DEBUG
Serial.begin(57600);
#endif
PRINTS("\n[MD_MAX72XX Test & Demo]");
// scrollText("MD_MAX72xx Test ");
}
void loop()
{
#if 1
scrollText("Graphics");
zeroPointSet();
rows();
columns();
cross();
stripe();
checkboard();
bullseye();
bounce();
spiral();
#endif
#if 1
scrollText("Control");
intensity();
scanLimit();
blinking();
#endif
#if 1
scrollText("Transform");
transformation1();
transformation2();
#endif
#if 1
scrollText("Charset");
wrapText();
showCharset();
#endif
}

View File

@@ -0,0 +1,106 @@
// Program to exercise the MD_MAX72XX library
//
// Test the library transformation functions with range subsets
#include <MD_MAX72xx.h>
#include <SPI.h>
// We always wait a bit between updates of the display
#define DELAYTIME 300 // in milliseconds
// 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 // 2, 4, 6, or 8 work best - see Z array
#define CLK_PIN 13 // or SCK
#define DATA_PIN 11 // or MOSI
#define CS_PIN 10 // or SS
// SPI hardware interface
MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
// Arbitrary pins
//MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES);
// Global variables
uint32_t lastTime = 0;
typedef struct
{
uint8_t startDev; // start of zone
uint8_t endDev; // end of zone
uint8_t ch; // character to show
MD_MAX72XX::transformType_t tt;
} zoneDef_t;
zoneDef_t Z[] =
{
#if MAX_DEVICES == 2
{0, 0, 26, MD_MAX72XX::TSR },
{1, 1, 27, MD_MAX72XX::TSL },
#endif // MAX_DEVICES 2
#if MAX_DEVICES == 4
{0, 0, 26, MD_MAX72XX::TSR },
{1, 1, 25, MD_MAX72XX::TSD },
{2, 2, 24, MD_MAX72XX::TSU },
{3, 3, 27, MD_MAX72XX::TSL },
#endif // MAX_DEVICES 4
#if MAX_DEVICES == 6
{0, 1, 26, MD_MAX72XX::TSR },
{2, 2, 24, MD_MAX72XX::TSU },
{3, 3, 25, MD_MAX72XX::TSD },
{4, 5, 27, MD_MAX72XX::TSL },
#endif // MAX_DEVICES 6
#if MAX_DEVICES == 8
{0, 1, 26, MD_MAX72XX::TSR },
{2, 2, 24, MD_MAX72XX::TSU },
{3, 3, 25, MD_MAX72XX::TSD },
{4, 4, 24, MD_MAX72XX::TSU },
{5, 5, 25, MD_MAX72XX::TSD },
{6, 7, 27, MD_MAX72XX::TSL },
#endif // MAX_DEVICES 8
};
#define ARRAY_SIZE(A) (sizeof(A)/sizeof(A[0]))
void runTransformation(void)
{
mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::OFF);
for (uint8_t i = 0; i < ARRAY_SIZE(Z); i++)
mx.transform(Z[i].startDev, Z[i].endDev, Z[i].tt);
mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::ON);
}
void setup()
{
Serial.begin(57600);
Serial.println("[Zone Transform Test]");
mx.begin();
mx.control(MD_MAX72XX::WRAPAROUND, MD_MAX72XX::ON);
// set up the display characters
for (uint8_t i = 0; i < ARRAY_SIZE(Z); i ++)
{
mx.clear(Z[i].startDev, Z[i].endDev);
for (uint8_t j = Z[i].startDev; j <= Z[i].endDev; j++)
mx.setChar(((j+1)*COL_SIZE)-2, Z[i].ch);
}
lastTime = millis();
// Enable the display
mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::ON);
}
void loop()
{
if (millis() - lastTime >= DELAYTIME)
{
runTransformation();
lastTime = millis();
}
}