top of page

8X8 LED Matrix Max7219 Control using Android app via Bluetooth

Updated: Dec 29, 2022


Here to learn how to control 8×8 LED Matrix using the MAX7219 driver (FC-16 Module) and Arduino board and android application. It is a two-dimensional patterned LED array that is used to represent event text message. This is a simple and somewhat inexpensive way of controlling a FC-16 module matrix. In addition they can be chained together to control two or more dot matrices, in this project we are used 8 and 12 module for scrolling and effect message and also effect setting are save, reset and clear using android app via bluetooth.

The android application can Provides control of:

Displayed message text and justification

Message frame Speed, pause time and inverted display

Display brightness

Saving parameters to EEPROM

Display configuration reset, Arduino hardware reset


For Programmed ardunio Uno & Price:


Demo for 12 Dot Matrix Module


8x8 LED Dot Matrix Display

A typical single color 8×8 dot matrix unit has 16 pins, 8 for each row and 8 for each column. so it contains a total of 64 LEDs. The reason for all rows and columns being wired together is to reduce the number of pins required.









Pin Configuration


MAX7219 LED Driver IC

The LED matrix can be driven in two ways. They are parallel (where each row or column are sent with parallel data) and serial (where the data is sent serially and an IC is used to convert this serial data into parallel data).



MAX7219 FC-16 Module Overview

There are several MAX7219 breakout boards available, two of which are more popular – one is the generic module and the other is the FC-16 module.


The first variable, HARDWARE_TYPE, tells the arduino which version of the module you are using.

  • Set the HARDWARE_TYPE to GENERIC_HW,

  • Set the HARDWARE_TYPE to FC16_HW,

MAX7219 Module Pinout

There will be two connectors on that module, they are Input Connector and Output Connector.

The breakout pins at one end of the module are used for communication with the microcontroller.


INPUT

VCC connects to 5V. Due to the high current draw of the display (up to 1A, if the brightness is cranked all the way up), it is recommended to run it directly from the external power supply instead of the 5V supply from the Arduino. Otherwise, be sure to keep the brightness below 50%, so that the Arduino’s voltage regulator does not overheat.


GND connects to the common ground.


DIN is the Data In. Connect it to any digital pin 11 of the Arduino.


CS is Chip Select (sometimes labeled as LOAD). Connect it to any digital pin 10 of the Arduino


CLK is the Clock pin. Connect it to any digital pin 13 of the Arduino.


OUTPUT:

he breakout pins at one end of the module are used for communication with the microcontroller.


VCC connects to 5V. Due to the high current draw of the display (up to 1A, if the brightness is cranked all the way up), it is recommended to run it directly from the external power supply instead of the 5V supply from the Arduino. Otherwise, be sure to keep the brightness below 50%, so that the Arduino’s voltage regulator does not overheat.


GND connects to the common ground.


DIN is the Data In. Connect it to any digital pin 11 of the Arduino.


CS is Chip Select (sometimes labeled as LOAD). Connect it to any digital pin 10 of the Arduino.


CLK is the Clock pin. Connect it to any digital pin 13 of the Arduino.


Circuit Diagram:


Components required:

1. ARDUINO

2. FC-16 4in1 MODULE 2 or 3 (2 demo in this project)


Subscribe and Download code.


Arduino Code:

#include <MD_Parola.h>

#include <MD_MAX72xx.h>

#include <SPI.h>

#include <EEPROM.h>


// BT interface serial interface

#define USE_ALTSOFTSERIAL 0


#if USE_ALTSOFTSERIAL

#include <AltSoftSerial.h>

#include <SoftwareSerial.h>


// MAX72xx 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 HARDWARE_TYPE MD_MAX72XX::FC16_HW

#define MAX_DEVICES 8 // here to change device used number yourself

#define CLK_PIN 13

#define DATA_PIN 11

#define CS_PIN 10


// Bluetooth Serial interface ---------

// Bluetooth Serial comms pins and parameters

const uint8_t BT_RECV_PIN = 3; // Arduino receive -> Bluetooth TxD pin

const uint8_t BT_SEND_PIN = 2; // Arduino send -> Bluetooth RxD pin

const char BT_NAME[] = "Parola";


// Define the type of hardware being used.

// Only one of these options is enabled at any time.

// The HM-10 is the JHHuaMao (HMSoft) version, not Bolutek. Line ending for AT commands differ.

#define HW_USE_HC05 0

#define HW_USE_HC06 1

#define HW_USE_HM10_HMSOFT 0

#define HW_USE_HM10_OTHER 0


#if HW_USE_HC05

const uint8_t HC05_SETUP_ENABLE = 7;


// Miscellaneous

#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))

#define BUF_SIZE 100 // message buffer size

#define EEPROM_START_ADDR 0 // EEPROM address for setup info


//=====================================================

//======= END OF USER CONFIGURATION PARAMETERS ========

//=====================================================


// Turn on debug statements to the serial output

#define DEBUG 0


#if DEBUG

#define PRINT(s, x) { Serial.print(F(s)); Serial.print(x); }

#define PRINTS(x) Serial.print(F(x))

#define PRINTX(x) Serial.println(x, HEX)

#define PRINT(s, x)

#define PRINTS(x)

#define PRINTX(x)





// Serial protocol parameters

const uint16_t BT_COMMS_TIMEOUT = 1000; // Protocol packet timeout period (start to end packet within this period)


const char PKT_START = '*'; // protocol packet start character

const char PKT_END = '~'; // protocol packet end character

const char PKT_ESC = '#'; // protocol packet escape character


const char PKT_CMD_SPEED = 'S'; // number (ms delay between frames)

const char PKT_CMD_BRIGHT = 'B'; // Toggle 0-15

const char PKT_CMD_RESET = 'R'; // Reset the Arduino hardware

const char PKT_CMD_FACSET = 'F'; // Factory settings

const char PKT_CMD_SAVE = 'W'; // Write current setup to EEPROM

const char PKT_CMD_MESSAGE = 'M'; // Displayed message

const char PKT_CMD_JUSTIFY = 'J'; // Toggle L, C, R

const char PKT_CMD_INVERT = 'V'; // Toggle Invert/Normal

const char PKT_CMD_TPAUSE = 'P'; // number (ms delay between in and out)

const char PKT_CMD_IANIM = 'I'; // In Animation - toggle through Animations table

const char PKT_CMD_OANIM = 'O'; // Out animation - toggle through Animations table


const char PKT_CMD_ACK = 'Z'; // Acknowledge command - data is PKT_ERR_* defines

const char PKT_ERR_OK = '0'; // no error/ok

const char PKT_ERR_TOUT = '1'; // timeout - start detected with no end within timeout period

const char PKT_ERR_CMD = '2'; // command field not valid or unknown

const char PKT_ERR_DATA = '3'; // data field not valid

const char PKT_ERR_SEQ = '4'; // generic protocol sequence error


const char *szStart = "AT+";

#if HW_USE_HC05

const char *szEnd = "\r\n";

const char PROGMEM ATCmd[] = {"NAME=\0ROLE=0\0CLASS=800500\0RESET\0\0"};

#if HW_USE_HC06

const char *szEnd = "\r\n";

const char PROGMEM ATCmd[] = {"NAME\0\0"};

#if HW_USE_HM10_HMSOFT

const char *szEnd = "";

const char PROGMEM ATCmd[] = {"NAME\0TYPE0\0ROLE0\0RESET\0\0"};

#if HW_USE_HM10_OTHER

const char *szEnd = "\r\n";

const char PROGMEM ATCmd[] = {"NAME\0TYPE0\0ROLE0\0RESET\0\0"};


// Data tables --------------

const textPosition_t textPosition[] = { PA_LEFT, PA_CENTER, PA_RIGHT };

const textEffect_t textEffect[] =

{

PA_PRINT, PA_SCROLL_UP, PA_SCROLL_LEFT, PA_SCROLL_RIGHT, PA_SCROLL_DOWN,

#if ENA_SCR_DIA

PA_SCROLL_UP_LEFT, PA_SCROLL_UP_RIGHT, PA_SCROLL_DOWN_LEFT, PA_SCROLL_DOWN_RIGHT,

#endif // ENA_SCR_DIA

#if ENA_WIPE

PA_WIPE, PA_WIPE_CURSOR,

#endif // ENA_WIPES

#if ENA_OPNCLS

PA_OPENING, PA_OPENING_CURSOR, PA_CLOSING, PA_CLOSING_CURSOR,

#endif // ENA_OPNCLS

#if ENA_GROW

PA_GROW_UP, PA_GROW_DOWN,

#endif // ENA_GROW

#if ENA_MISC

PA_SLICE, PA_MESH, PA_FADE, PA_DISSOLVE, PA_BLINDS, PA_RANDOM,

#endif //ENA_MISC

#if ENA_SCAN

PA_SCAN_HORIZ, PA_SCAN_VERT,

#endif // ENA_SCAN

};


// Global Variables ------------------


// Parola with HARDWARE SPI

MD_Parola P = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);

// SOFTWARE SPI

//MD_Parola P = MD_Parola(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES);


#if USE_ALTSOFTSERIAL

AltSoftSerial BTChan = AltSoftSerial();

SoftwareSerial BTChan = SoftwareSerial(BT_RECV_PIN, BT_SEND_PIN);


#define SIG0 0x55

#define SIG1 0xaa

struct globalData

{

uint8_t signature[2]; // recognise this as a valid setup


uint16_t scrollSpeed; // frame delay value in ms

uint16_t scrollPause; // msg pause in ms


uint8_t effectI; // in effect index into table

uint8_t effectO; // out effect index into table

uint8_t align; // justification index into table


uint8_t intensity; // display intensity

bool bInvert; // display inverted


char msg[BUF_SIZE+1]; // the message being displayed

} G;


// Global message buffers shared by BT and Scrolling functions

char newMessage[BUF_SIZE+1] = { '\0' };

bool newMessageAvailable = false;

bool newConfigAvailable = false;


// Application Code ------------------

void(*hwReset) (void) = 0; //declare reset function @ address 0


void writeGlobal(void)

{

PRINTS("\nSaving Global");

EEPROM.put(EEPROM_START_ADDR, G);

}


void readGlobal(bool bInit = false)

{

PRINTS("\nLoading Global");

EEPROM.get(EEPROM_START_ADDR, G);


if (bInit || G.signature[0] != SIG0 || G.signature[1] != SIG1)

// set the default parameters

{

PRINTS("\nInitialising Global");

G.signature[0] = SIG0;

G.signature[1] = SIG1;

G.scrollSpeed = 25;

G.scrollPause = 2000;

G.effectI = G.effectO = 0;

G.align = 1;

G.intensity = 7;

G.bInvert = false;

strcpy(G.msg, "Setup");


writeGlobal();

}


newConfigAvailable = true;

}


bool BT_getATCmd(char* szBuf, uint8_t lenBuf, bool fReset = true)

// Copy the AT command from PROGMEM into the buffer provided

// The first call should reset the index counter

// Return true if this is the last command

{

static uint16_t cmdIdx;


if (fReset) cmdIdx = 0;


strncpy_P(szBuf, ATCmd + cmdIdx, lenBuf);

cmdIdx += (strlen_P(ATCmd + cmdIdx) + 1);


return(pgm_read_byte(ATCmd + cmdIdx) == '\0');

}


bool BT_getATResponse(char* resp, uint8_t lenBuf)

// Get an AT response from the BT module or time out waiting

{

const uint16_t RESP_TIMEOUT = 500;


uint32_t timeStart = millis();

char c = '\0';

uint8_t len = 0;


*resp = '\0';

while ((millis() - timeStart < RESP_TIMEOUT) && (c != '\n') && (len < lenBuf))

{

if (BTChan.available())

{

c = BTChan.read();

*resp++ = c;

*resp = '\0';

len++;

}

}


return(len != '\0');

}


void BT_sendACK(char resp)

// Send a protocol ACK to the BT master

{

static char msg[] = { PKT_START, PKT_CMD_ACK, PKT_ERR_OK, PKT_END, '\n', '\0' };

msg[2] = resp;

PRINT("\nResp: ", msg);

BTChan.print(msg);

BTChan.flush();

}


void BT_begin(void)

// initialise the BT device for different hardware

{

const uint16_t BAUD = 9600;

char szCmd[20], szResp[16];

uint8_t i = 0;

bool fLast = false;


PRINT("\nStart BT connection at ", BAUD);

BTChan.begin(BAUD);


#if HW_USE_HC05

// Switch the HC05 to setup mode using digital I/O

pinMode(HC05_SETUP_ENABLE, OUTPUT);

digitalWrite(HC05_SETUP_ENABLE, HIGH);

delay(10); // just a small amount of time

digitalWrite(HC05_SETUP_ENABLE, LOW);




// Process all the AT commands for the selected BT module

// Send each command, read the response (or time out) and then

// do the next one.

// First item is always the name!

do

{

fLast = BT_getATCmd(szCmd, ARRAY_SIZE(szCmd), (i == 0));


// Print the preamble, AT command, end of line by assembling the

// data into a string of allocated memory. This allows the data to

// send out in one hit rather than piecemeal.

char *sz = (char *)malloc((strlen(szStart) + strlen_P(szCmd) + \

strlen(BT_NAME) + strlen(szEnd) + 1) * sizeof(char));

strcpy(sz, szStart);

strcat(sz, szCmd);

if (i == 0) // first item - insert the name

strcat(sz, BT_NAME);

strcat(sz, szEnd);

BTChan.print(sz);

BTChan.flush();


free(sz);

i++;


// Wait for and get the response, except for the

// last one when we don't care as normally a RESET.

if (!fLast)

{

if (!BT_getATResponse(szResp, ARRAY_SIZE(szResp)))

{

PRINT("\nBT err on ", szCmd);

PRINT(":", szResp);

}

}

} while (!fLast);


BTChan.flush();

}


uint8_t BT_executeCommand(uint8_t cmd, char *pd)

{

uint8_t sts = PKT_ERR_OK; // assume all ok ...


PRINT("\nexecuting ", (char)cmd);

PRINT(" with '", pd);

PRINTS("'");


switch (cmd)

{

case PKT_CMD_RESET:

hwReset();

break;


case PKT_CMD_FACSET:

readGlobal(true);

PRINTS("\nCMD Factory Settings");

break;


case PKT_CMD_BRIGHT:

G.intensity = (G.intensity + 1) % (MAX_INTENSITY + 1);

PRINT("\nCMD Brightness ", G.intensity);

break;


case PKT_CMD_IANIM:

G.effectI = (G.effectI + 1) % ARRAY_SIZE(textEffect);

PRINT("\nCMD In Animation ", G.effectI);

break;


case PKT_CMD_OANIM:

G.effectO = (G.effectO + 1) % ARRAY_SIZE(textEffect);

PRINT("\nCMD Out Animation ", G.effectO);

break;


case PKT_CMD_INVERT:

G.bInvert = !P.getInvert();

PRINT("\nCMD Invert ", G.bInvert ? "on" : "off");

break;


case PKT_CMD_JUSTIFY:

G.align = (G.align + 1) % ARRAY_SIZE(textPosition);

PRINT("\nCMD Text Align ", G.align);

break;


case PKT_CMD_SAVE:

writeGlobal();

PRINTS("\nCMD Save");

break;


case PKT_CMD_MESSAGE:

strcpy(newMessage, pd);

newMessageAvailable = true;

PRINT("\nCMD Message '", newMessage); PRINTS("'");

break;


case PKT_CMD_SPEED:

case PKT_CMD_TPAUSE:

{

int16_t v = atoi(pd);


if (v < 0 || v > 10000)

sts = PKT_ERR_DATA;

else

{

if (cmd == PKT_CMD_SPEED)

{

G.scrollSpeed = v;

PRINT("\nCMD Speed ", v);

}

else

{

G.scrollPause = v;

PRINT("\nCMD Pause ", v);

}

}

}

break;


default:

sts = PKT_ERR_CMD;

PRINT("CMD Error - ", cmd);

break;

}


// set global flags

newConfigAvailable = (sts == PKT_ERR_OK && cmd != PKT_CMD_MESSAGE);


return(sts);

}


void BT_getCommand(void)

// Call repeatedly to receive and process characters waiting in the serial queue

// Return true when a good message is fully received

{

static enum { ST_IDLE, ST_CMD, ST_DATA, ST_END } state = ST_IDLE;

static uint32_t timeStart = 0;

static char cBuf[BUF_SIZE+1];

static uint16_t countBuf;

static char cmd = '\0';

static bool prevEsc, inEsc = false;


// check for timeout if we are currently mid-packet

if (state != ST_IDLE)

{

if (millis() - timeStart >= BT_COMMS_TIMEOUT)

{

BT_sendACK(PKT_ERR_TOUT);

timeStart = 0;

state = ST_IDLE;

}

}


// process the next character if there is one

if (BTChan.available())

{

char ch = BTChan.read();


// deal with the escape sequence indicator

prevEsc = inEsc;

inEsc = (ch == PKT_ESC && !prevEsc);


// now run the FSM

switch (state)

{

case ST_IDLE: // waiting start character

if (!prevEsc && ch == PKT_START)

{

PRINT("\nPkt Srt ", ch);

state = ST_CMD;

cmd = cBuf[0] = '\0';

timeStart = millis();

countBuf = 0;

}

break;


case ST_CMD: // reading command

cmd = ch; // save the command until we have a full protocol packet

PRINT("\nPkt Cmd ", cmd);


switch (ch)

{

case PKT_CMD_RESET:

case PKT_CMD_FACSET:

case PKT_CMD_BRIGHT:

case PKT_CMD_IANIM:

case PKT_CMD_OANIM:

case PKT_CMD_INVERT:

case PKT_CMD_JUSTIFY:

case PKT_CMD_SAVE:

case PKT_CMD_MESSAGE:

case PKT_CMD_SPEED:

case PKT_CMD_TPAUSE:

state = ST_DATA;

break;


default:

BT_sendACK(PKT_ERR_CMD);

cmd = '\0';

state = ST_IDLE;

break;

}

break;


case ST_DATA: // reading data

if (countBuf >= BUF_SIZE) // message too large - stop processing it now

{

PRINTS("\nBuffer overflow");

BT_sendACK(PKT_ERR_DATA);

state = ST_IDLE;

}

else if (ch == PKT_END && !prevEsc) // end character not escaped

{

PRINT("\nPkt end @", countBuf);

cBuf[countBuf] = '\0'; // terminate the string

BT_sendACK(BT_executeCommand(cmd, cBuf));

state = ST_IDLE;

}

else if (!inEsc) // not escaping, so save this

{

PRINT("\nPkt cBuf[", countBuf); PRINT("]:", ch);

cBuf[countBuf++] = ch;

}

break;


default: // something screwed up - reset the FSM

state = ST_IDLE;

BT_sendACK(PKT_ERR_SEQ);

break;

}

}

}


void setup()

{

#if DEBUG

Serial.begin(57600);

PRINTS("\n[Parola_BT_Control Debug]");


readGlobal();


P.begin();

P.displayClear();

BT_begin();

}


void loop()

{

// get the next thing from the BT channel

BT_getCommand();


// if the config has changed, need to change the display parameters

if (newConfigAvailable)

{

PRINTS("\nSetting new config");

P.setSpeed(G.scrollSpeed);

P.setPause(G.scrollPause);

P.setTextEffect(textEffect[G.effectI], textEffect[G.effectO]);

P.setTextAlignment(textPosition[G.align]);

P.setIntensity(G.intensity);

P.setInvert(G.bInvert);

P.setTextBuffer(G.msg);

P.displayReset();


newConfigAvailable = false;

}


// if we have a new message, copy it over

if (newMessageAvailable)

{

PRINTS("\nSetting new message");

strcpy(G.msg, newMessage);

P.displayReset();


newMessageAvailable = false;

}


// keep the animation going in all cases

if (P.displayAnimate())

P.displayReset();

}


Demo:

8 dot matrix module


Pay and get arduino code and .aia file.


Android application:

Download app:









1,179 views2 comments

2 Comments


Can I get the aia for the app please?

Like

rpelle
rpelle
Jan 29, 2022

Hi,

Great article and video. Learned a lot. By any chance could you upload a photo of the "backside of the displays". I'm new to all this and am having a hard time connecting two 4 in 1 displays. I'm also not that good at soldering, so I'm looking for simplest way to connect then. Thank You

Like
bottom of page