Disply dot at cordinates with parola library

I have made a code that displays text on a dot matrix with the parola library. like so
P.print(displayChar);
I need to display text at a certain coordinate like so
mx.setPoint(gamePlayer, 0, true);

I tried using the <MD_MAX72xx.h> library to do so, but it seemed to interfere with the parola library.

Is there some way to display dots at cordinates using the parola library?

@marco_c is the author of the libraries and is a member here

LED matrix (max7219)?

Yes, an 8 x 32 max 7219 module.:slightly_smiling_face:

I’m not completely sure, but Parola doesn’t really let you set individual pixels directly because it manages its own screen buffer. Using MD_MAX72XX at the same time usually causes conflicts, which is why you’re seeing interference.

If you need pixel-level drawing, you generally have to use Parola’s low-level buffer access (like the zone/graphics object functions) or switch fully to MD_MAX72XX for manual control.

Maybe @marco_c can confirm the simplest way, but mixing both libraries at the same time usually causes issues.

Can MD_MAX72XX handle text?

You could always create your own font and print characters by using the setPoint() function

There are a couple of ways to do this. Maybe if you can describe what you are trying to achieve with the display (ie, your problem) I can suggest the right approach.

Thanks for replying @marco_c

Thank you, @marco_c !

I have made a clock project that uses the MD_Parola library to show time and a menu. And i made a falling blcoks game for the same 8x32 max7219 module and rotary encoder. I want to merge them to make a clock that has a built in game accesable from the menu.

Clock:

////////////////////ARDUINO UNO R4////////////////////
////////////////////LED MATRIX CLOCK////////////////////

////////////////////LIBRARIES////////////////////

#include "RTClib.h"
#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include <SPI.h>
#include <CtrlBtn.h>
#include <CtrlEnc.h>
#include <BlockNot.h>

////////////////////VARIABLES////////////////////

static char displayChar[9] = {0}; //stores the text for the display to show
char status = 'N';
//  status variable — indicates the clock's current mode.
//
//  Legend:
//   'N' - Normal display (time view)
//   'h' - Enter hour-edit menu
//   'm' - Enter minute-edit menu
//   'H' - Hour edit screen
//   'M' - Minute edit screen
//   's' - Set/confirm time
//   'e' - Exit menu / return to normal
byte oldMin;        //holds the last minute so screen updates when minute changes
int8_t setMinuteVar = 0;  //holds the value when using the encoder to change the minute
int8_t setHourVar = 0;    //holds the value when using the encoder to change the Hour

////////////////////PAROLA SETUP////////////////////

#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define MAX_DEVICES 4
#define CS_PIN 8
MD_Parola P = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);

////////////////////RTC SETUP////////////////////

RTC_DS1307 rtc;
char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};

////////////////////BLOCKNOT SETUP////////////////////

BlockNot rtcRefreshTimer(1000);//check if time has changed once a second
BlockNot menuTimer(5000);//check if time has changed once a second

////////////////////CTRL SETUP AND MENU HANDLERS////////////////////
//Switch...Case is used to handle the status of the variables
// Define an onTurnleft handler.
void onTurnLeft() {
  menuTimer.start(WITH_RESET);//resets the timer that automatically exits the menu
  switch (status) {
      case 'm':
        status = 'h';
        P.displayClear();
        P.print("Hours");
        break;
      case 'H':
      setHourVar--;
      if(setHourVar <= -1){setHourVar = 23;}
      snprintf(displayChar, sizeof(displayChar), "%02d", setHourVar);
      P.displayClear();
      P.print(displayChar); 
        break;
      case 'M':
      setMinuteVar--;
      if(setMinuteVar <= -1){setMinuteVar = 59;}
      snprintf(displayChar, sizeof(displayChar), "%02d", setMinuteVar);
      P.displayClear();
      P.print(displayChar);
        break;
      case 's':
        status = 'm';
        P.displayClear();
        P.print("Minutes");  
        break;
      case 'e':
        status = 's';
        P.displayClear();
        P.print("Set"); 
        break;
      default:
        break;
    }
}

// Define an onTurnRight handler.
void onTurnRight() {
  menuTimer.start(WITH_RESET);
    switch (status) {
      case 'h':
        status = 'm';
        P.displayClear();
        P.print("Minutes"); 
        break;
      case 'm':
        status = 's';
        P.displayClear();
        P.print("Set"); 
        break;
      case 'H':
      setHourVar++;
      if(setHourVar >= 24){setHourVar = 0;}
      snprintf(displayChar, sizeof(displayChar), "%02d", setHourVar);
      P.displayClear();
      P.print(displayChar); 
        break;
      case 'M':
      setMinuteVar++;
      if(setMinuteVar >= 60){setMinuteVar = 0;}
      snprintf(displayChar, sizeof(displayChar), "%02d", setMinuteVar);
      P.displayClear();
      P.print(displayChar);
        break;
      case 's':
        status = 'e';
        P.displayClear();
        P.print("Exit"); 
        break;
      default:
        break;
    }
}

// Define an onPress handler.
void onPress() {
  menuTimer.start(WITH_RESET);
  DateTime now = rtc.now();
    switch (status) {
      case 'N':
        setHourVar = now.hour();
        setMinuteVar = now.minute();
        status = 'h';
        P.displayClear();
        P.print("Hours"); 
        break;
      case 'h':
        status = 'H';
        snprintf(displayChar, sizeof(displayChar), "%02d", setHourVar);
        P.displayClear();
        P.print(displayChar); 
        break;
      case 'm':
        status = 'M';
        snprintf(displayChar, sizeof(displayChar), "%02d", setMinuteVar);
        P.displayClear();
        P.print(displayChar); 
        break;
      case 'H':
        status = 'h'; 
        P.displayClear();
        P.print("Hours"); 
        break;
      case 'M':
        status = 'm';
        P.displayClear();
        P.print("Minutes"); 
        break;
      case 's':
        status = 'N';
        setTime(setHourVar, setMinuteVar);
        showTime();
        break;
      case 'e':
        status = 'N';
        showTime();
        break;
      default:
        break;
    }
}

CtrlBtn button(2, 50, onPress);
CtrlEnc encoder(4, 3, onTurnLeft, onTurnRight);

////////////////////TIME SETTING FUNCTIONS////////////////////

void setTime(int8_t newHour, int8_t newMinute) {//take two variables and sets the time of the rtc to them
  DateTime before = rtc.now();
  DateTime updated(before.year(), before.month(), before.day(), newHour, newMinute, 0);
  rtc.adjust(updated);
}

void showTime() {//updates the display and shows the current time.
  DateTime now = rtc.now();
  oldMin = now.minute();
  if( status == 'N'){
    snprintf(displayChar, sizeof(displayChar), "%02d:%02d", now.hour(), now.minute());
    P.displayClear(); 
    P.print(displayChar);
  }
}

////////////////////SETUP CODE////////////////////

void setup () {
  P.begin();
  P.setIntensity(0);
  if (! rtc.begin()) {
    while (1) delay(10);
  }
  if (! rtc.isrunning()) {
    // When time needs to be set on a new device, or after a power loss, the
    // following line sets the RTC to the date & time this sketch was compiled
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  }
  DateTime now = rtc.now();
  oldMin = now.minute();
  P.setTextAlignment(PA_CENTER);
  P.print("Hello!");//Turns the screen on and says hello
  delay(4000);
  showTime();
  menuTimer.stop();
  rtcRefreshTimer.start(WITH_RESET);
}

void loop () {
  if (menuTimer.triggered()) {//This exits the menu automatically
    switch (status) {
      case 'h':
        status = 'N';
        showTime();
        menuTimer.stop();
        break;
      case 'm':
        status = 'N';
        showTime();
        menuTimer.stop();
        break;
      case 'H':
        status = 'h';
        P.displayClear();
        P.print("Hours"); 
        break;
      case 'M':
        status = 'm';
        P.displayClear();
        P.print("Minutes"); 
        break;
      case 's':
        status = 'N';
        showTime();
        menuTimer.stop();
        break;
      case 'e':
        status = 'N';
        showTime();
        menuTimer.stop();
        break;
      default:
        break;
    }

  }

  if(rtcRefreshTimer.triggered()){//checks if time has changed
    DateTime now = rtc.now();
    if (oldMin != now.minute()) {
      showTime();
    }
  }
  encoder.process();
  button.process();
}

Game:

#include <MD_MAX72xx.h>
#include <SPI.h>
#include <CtrlEnc.h>
#include <BlockNot.h>

#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define MAX_DEVICES 4
#define CS_PIN 8  // Your chip select pin

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

BlockNot blockTimer(200);

uint8_t blocks[32] = {255, 255, 255, 255, 255, 255, 255, 255, 
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, };

int8_t player = 0;


void onTurnRight() {
  if (player - 1 != blocks[0]) {
    if(((player == 0) && (blocks[0] == 7)) == 0){
      player--; 
      if(player < 0){
        player = 7;
      } 
    }
  }
  updateScreen();
}

void onTurnLeft() {
  if (player + 1 != blocks[0]) {
    if(((player == 7) && (blocks[0] == 0)) == 0){
      player++; 
      if(player > 7){
        player = 0;
      } 
    }
  }
  updateScreen();
}

CtrlEnc encoder(4, 3, onTurnLeft, onTurnRight);

void setup() {
  Serial.begin(115200);
  P.begin();
  P.control(MD_MAX72XX::INTENSITY, 0);  // Set brightness level (0–15)
  blocks[31] = rand() % 8;
  P.clear();
  updateScreen();
}

void loop() {
  encoder.process();
  if(blockTimer.TRIGGERED){
    moveBlocks();
  }
}

void moveBlocks() {
  for (uint8_t i = 1; i <= 31; i++) {
    if (blocks[i] != 255) {
      blocks[i - 1] = blocks[i];
    }
  }
  blocks[31] = rand() % 8;
  if (blocks[0] == player){
    delay(2000);
    for (uint8_t i = 0; i <= 31; i++) {
      blocks[i] = 255;
    }
  }
  updateScreen();
}

void updateScreen() {
  P.clear();
  P.setPoint(player, 0, true);
  for(uint8_t i = 0; i <= 31; i++){
    if(blocks[i] != 255){
      P.setPoint(blocks[i], i, true);
    }
  }
}

Ok, this is pretty straightforward as it looks like it is either clock display OR playing the game (ie, the two are separate and will either run your clock code or the game code based on some kind of decision).

Parola uses the MAX72XX library to drive the hardware and it assumes that it will have full access to the hardware while the animations are running (this includes print()). However, while you are not displaying any text animations then you can take over and run the display as you wish.

You need to access the underlying MD_MAX72xx object using the Parola getGraphicObject() method. This will return a pointer to the MD_MAX72XX object and you can then use the methods from MD_MAX72xx by invoking them using the syntax MAX72xx_ptr→methodName(). This avoids any conflicts as you are using the same display buffers for both Parola and your own graphics.

Hope this is clear.

If you want to mix text and graphics at the same time then this can also be done but is more complex (see this blog article Parola A to Z – Mixing Text and Graphics – Arduino++).

@marco_c
Thank you!:blush:
Is there an example of how I can use the getGraphicObject()?

No, not really because it is just getting a pointer and then using it to invoke MD_MAX72xx methods.

MD_Parola P = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
MD_MAX72XX *pM = P.getGraphicObject();
…
pM->setPoint(r, c, state);   // use MD_MAX72xx library methods

Thank you! I will combining my code that way.

I combined the code like so and it is now working, thank you so much @marco_c

////////////////////ARDUINO UNO R4////////////////////
////////////////////LED MATRIX CLOCK////////////////////

////////////////////LIBRARIES////////////////////

#include "RTClib.h"
#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include <SPI.h>
#include <CtrlBtn.h>
#include <CtrlEnc.h>
#include <BlockNot.h>

////////////////////VARIABLES////////////////////

static char displayChar[9] = {0}; //stores the text for the display to show
char status = 'N';
//  status variable — indicates the clock's current mode.
//
//  Legend:
//   'N' - Normal display (time view)
//   'h' - Enter hour-edit menu
//   'm' - Enter minute-edit menu
//   'H' - Hour edit screen
//   'M' - Minute edit screen
//   's' - Set/confirm time
//   'e' - Exit menu / return to normal
uint8_t oldMin;        //holds the last minute so screen updates when minute changes
int8_t setMinuteVar = 0;  //holds the value when using the encoder to change the minute
int8_t setHourVar = 0;    //holds the value when using the encoder to change the Hour
uint8_t gameBlocks[32] = {255, 255, 255, 255, 255, 255, 255, 255, 
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, };
int8_t gamePlayer = 0;
////////////////////PAROLA SETUP////////////////////

#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define MAX_DEVICES 4
#define CS_PIN 8
MD_Parola P = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
MD_MAX72XX *mx;

////////////////////RTC SETUP////////////////////

RTC_DS1307 rtc;
char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};

////////////////////BLOCKNOT SETUP////////////////////

BlockNot rtcRefreshTimer(1000);//check if time has changed once a second
BlockNot menuTimer(5000);//check if time has changed once a second
BlockNot gameBlockTimer(200);

////////////////////CTRL SETUP AND MENU HANDLERS////////////////////
//Switch...Case is used to handle the status of the variables
// Define an onTurnleft handler.
void onTurnLeft() {
  menuTimer.start(WITH_RESET);//resets the timer that automatically exits the menu
  switch (status) {
      case 'm':
        status = 'h';
        P.displayClear();
        P.print("Hours");
        break;
      case 'H':
      setHourVar--;
      if(setHourVar <= -1){setHourVar = 23;}
      snprintf(displayChar, sizeof(displayChar), "%02d", setHourVar);
      P.displayClear();
      P.print(displayChar); 
        break;
      case 'M':
      setMinuteVar--;
      if(setMinuteVar <= -1){setMinuteVar = 59;}
      snprintf(displayChar, sizeof(displayChar), "%02d", setMinuteVar);
      P.displayClear();
      P.print(displayChar);
        break;
      case 's':
        status = 'm';
        P.displayClear();
        P.print("Minutes");  
        break;
      case 'e':
        status = 's';
        P.displayClear();
        P.print("Set"); 
        break;
      case 'g':
        status = 'e';
        P.displayClear();
        P.print("Exit"); 
        break;
      case 'G':
        if (gamePlayer + 1 != gameBlocks[0]) {
          if(((gamePlayer == 7) && (gameBlocks[0] == 0)) == 0){
            gamePlayer++; 
            if(gamePlayer > 7){
              gamePlayer = 0;
            } 
          }
        }
        gameUpdateScreen();
        break;
      default:
        break;
    }
}

// Define an onTurnRight handler.
void onTurnRight() {
  menuTimer.start(WITH_RESET);
    switch (status) {
      case 'h':
        status = 'm';
        P.displayClear();
        P.print("Minutes"); 
        break;
      case 'm':
        status = 's';
        P.displayClear();
        P.print("Set"); 
        break;
      case 'H':
      setHourVar++;
      if(setHourVar >= 24){setHourVar = 0;}
      snprintf(displayChar, sizeof(displayChar), "%02d", setHourVar);
      P.displayClear();
      P.print(displayChar); 
        break;
      case 'M':
      setMinuteVar++;
      if(setMinuteVar >= 60){setMinuteVar = 0;}
      snprintf(displayChar, sizeof(displayChar), "%02d", setMinuteVar);
      P.displayClear();
      P.print(displayChar);
        break;
      case 's':
        status = 'e';
        P.displayClear();
        P.print("Exit"); 
        break;
      case 'e':
        status = 'g';
        P.displayClear();
        P.print("Game"); 
        break;
      case 'G':
        if (gamePlayer - 1 != gameBlocks[0]) {
          if(((gamePlayer == 0) && (gameBlocks[0] == 7)) == 0){
            gamePlayer--; 
            if(gamePlayer < 0){
              gamePlayer = 7;
            } 
          }
        }
        gameUpdateScreen();        
        break;
      default:
        break;
    }
}

// Define an onPress handler.
void onPress() {
  menuTimer.start(WITH_RESET);
  DateTime now = rtc.now();
    switch (status) {
      case 'N':
        setHourVar = now.hour();
        setMinuteVar = now.minute();
        status = 'h';
        P.displayClear();
        P.print("Hours"); 
        break;
      case 'h':
        status = 'H';
        snprintf(displayChar, sizeof(displayChar), "%02d", setHourVar);
        P.displayClear();
        P.print(displayChar); 
        break;
      case 'm':
        status = 'M';
        snprintf(displayChar, sizeof(displayChar), "%02d", setMinuteVar);
        P.displayClear();
        P.print(displayChar); 
        break;
      case 'H':
        status = 'h'; 
        P.displayClear();
        P.print("Hours"); 
        break;
      case 'M':
        status = 'm';
        P.displayClear();
        P.print("Minutes"); 
        break;
      case 's':
        status = 'N';
        setTime(setHourVar, setMinuteVar);
        showTime();
        break;
      case 'e':
        status = 'N';
        showTime();
        break;
      case 'g':
      status = 'G';
      P.displayClear();
      P.displaySuspend(true);
      playGame();
        break;
      case 'G':
      status = 'g';
      for (uint8_t i = 0; i <= 31; i++) {
        gameBlocks[i] = 255;
      }
      P.displaySuspend(false);
      P.displayClear();
      P.print("Game"); 
        break;
      default:
        break;
    }
}

CtrlBtn button(2, 50, onPress);
CtrlEnc encoder(4, 3, onTurnLeft, onTurnRight);

////////////////////TIME SETTING FUNCTIONS////////////////////

void setTime(int8_t newHour, int8_t newMinute) {//take two variables and sets the time of the rtc to them
  DateTime before = rtc.now();
  DateTime updated(before.year(), before.month(), before.day(), newHour, newMinute, 0);
  rtc.adjust(updated);
}

void showTime() {//updates the display and shows the current time.
  DateTime now = rtc.now();
  oldMin = now.minute();
  if( status == 'N'){
    snprintf(displayChar, sizeof(displayChar), "%02d:%02d", now.hour(), now.minute());
    P.displayClear(); 
    P.print(displayChar);
  }
}

////////////////////SETUP CODE////////////////////

void setup () {
  P.begin();
  P.setIntensity(0);
  mx = P.getGraphicObject();   // store pointer globally
  if (! rtc.begin()) {
    while (1) delay(10);
  }
  if (! rtc.isrunning()) {
    // When time needs to be set on a new device, or after a power loss, the
    // following line sets the RTC to the date & time this sketch was compiled
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  }
  DateTime now = rtc.now();
  oldMin = now.minute();
  P.setTextAlignment(PA_CENTER);
  P.print("Hello!");//Turns the screen on and says hello
  delay(4000);
  showTime();
  menuTimer.stop();
  rtcRefreshTimer.start(WITH_RESET);
}

void loop () {
  if (menuTimer.triggered()) {//This exits the menu automatically
    switch (status) {
      case 'h':
        status = 'N';
        showTime();
        menuTimer.stop();
        break;
      case 'm':
        status = 'N';
        showTime();
        menuTimer.stop();
        break;
      case 'H':
        status = 'h';
        P.displayClear();
        P.print("Hours"); 
        break;
      case 'M':
        status = 'm';
        P.displayClear();
        P.print("Minutes"); 
        break;
      case 's':
        status = 'N';
        showTime();
        menuTimer.stop();
        break;
      case 'e':
        status = 'N';
        showTime();
        menuTimer.stop();
        break;
      default:
        break;
    }

  }

  if(rtcRefreshTimer.triggered()){//checks if time has changed
    DateTime now = rtc.now();
    if (oldMin != now.minute()) {
      showTime();
    }
  }
  encoder.process();
  button.process();
}

void playGame(){
  gameUpdateScreen();
  while (status == 'G') {
    encoder.process();
    button.process();
    if(gameBlockTimer.TRIGGERED){
      gameMoveBlocks();
    }
  }
}
void gameMoveBlocks() {
  for (uint8_t i = 1; i <= 31; i++) {
    if (gameBlocks[i] != 255) {
      gameBlocks[i - 1] = gameBlocks[i];
    }
  }
  gameBlocks[31] = rand() % 8;
  if (gameBlocks[0] == gamePlayer){
    delay(2000);
    for (uint8_t i = 0; i <= 31; i++) {
      gameBlocks[i] = 255;
    }
  }
  gameUpdateScreen();
}

void gameUpdateScreen() {
  mx->clear();
  mx->setPoint(gamePlayer, 0, true);
  for(uint8_t i = 0; i <= 31; i++){
    if(gameBlocks[i] != 255){
      mx->setPoint(gameBlocks[i], i, true);
    }
  }
}
1 Like