Max72xxPanel library

Grumpy_Mike:
But at least she is consistent :slight_smile:

Glad I could help in getting it sorted.

True. And as it stands today, I believe consistency is what the world needs more of. A lot more!!

I look forward to learning more here.

Cheers!!

Hi,

Apologies in advance for a longish post but I want to put in as much details as I can. I was not sure if I should start a new topic since the question relates to the same project but involves the code and I did not want to cross post.

The idea of the project was to make a 16x64 audio spectrum analyzer with the FFT running on a laptop on processing sending data to an HC-05 BT module to a standalone Arduino that drives the Max7219 displays. I have been able to verify that the 8x8 modules work fine. The processing code that I am using is adapted from here:

import processing.serial.*;
//import processing.sound.*;
import ddf.minim.analysis.*;
import ddf.minim.*;


Minim minim;
AudioInput in;
FFT fft;
int buffer_size = 4096; 
float sample_rate = 200000;
int freq_width = 150;
int bands = 64;
Serial port1;
float[] spectrum = new float[bands];
int [ ]freq_array = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; 
float[] freq_height = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};

void settings()
{
 size(512, 360);
}
void setup() {
 
  background(255);
    
  // Create an Input stream which is routed into the Amplitude analyzer
  minim = new Minim(this);
  
  // start the Audio Input
  in = minim.getLineIn(Minim.MONO,buffer_size, sample_rate);
  
  // patch the AudioIn
  // create an FFT object that has a time-domain buffer 
  // the same size as line-in's sample buffer
  fft = new FFT(in.bufferSize(), in.sampleRate());
  // Tapered window important for log-domain display
  fft.window(FFT.HAMMING);
  port1 = new Serial (this, "COM5", 115200);
}      

void draw() { 
  background(255);
    
for(int k=0; k<64; k++){
freq_array[k] = 0;
}

 fft.forward(in.mix);
 
  freq_height[0] = fft.calcAvg((float) 0, (float) 30);
  freq_height[1] = fft.calcAvg((float) 31, (float) 60);
  freq_height[2] = fft.calcAvg((float) 61, (float) 100);
  freq_height[3] = fft.calcAvg((float) 101, (float) 150);
  freq_height[4] = fft.calcAvg((float) 151, (float) 200);
  freq_height[5] = fft.calcAvg((float) 201, (float) 250);
  freq_height[6] = fft.calcAvg((float) 251, (float) 300);
  freq_height[7] = fft.calcAvg((float) 301, (float) 350);
  freq_height[8] = fft.calcAvg((float) 351, (float) 400); 
  
  for(int n = 9; n < 63; n++)
  {
  freq_height[n] = fft.calcAvg((float) (351+(freq_width*(n-9))), (float) (500+(freq_width*(n-9))));
  }
  
  freq_height[63] = (fft.calcAvg((float) 20, (float) 60));
  
  float x = 8;
  float y = 3;
  for(int j=0; j<64; j++){    
    freq_height[j] = freq_height[j]*(log(x)/y);
    x = x + (x); 
  }
  
  for(int j=0; j<64; j++){    
    if (freq_height[j] < 2000 && freq_height[j] > 180){freq_array[j] = 16;}
    else{ if (freq_height[j] <= 180 && freq_height[j] > 160){freq_array[j] = 15;}
    else{ if (freq_height[j] <= 160 && freq_height[j] > 130){freq_array[j] = 14;}
    else{ if (freq_height[j] <= 130 && freq_height[j] > 110){freq_array[j] = 13;}
    else{ if (freq_height[j] <= 110 && freq_height[j] > 90){freq_array[j] = 12;}
    else{ if (freq_height[j] <= 90 && freq_height[j] > 70){freq_array[j] = 11;}
    else{ if (freq_height[j] <= 70 && freq_height[j] > 60){freq_array[j] = 10;}
    else{ if (freq_height[j] <= 60 && freq_height[j] > 50){freq_array[j] = 9;}
    else{ if (freq_height[j] <= 50 && freq_height[j] > 40){freq_array[j] = 8;}
    else{ if (freq_height[j] <= 40 && freq_height[j] > 30){freq_array[j] = 7;}
    else{ if (freq_height[j] <= 30 && freq_height[j] > 20){freq_array[j] = 6;}
    else{ if (freq_height[j] <= 20 && freq_height[j] > 15){freq_array[j] = 5;}
    else{ if (freq_height[j] <= 15 && freq_height[j] > 11){freq_array[j] = 4;}
    else{ if (freq_height[j] <= 11 && freq_height[j] > 8){freq_array[j] = 3;}
    else{ if (freq_height[j] <= 8 && freq_height[j] > 5){freq_array[j] = 2;}
    else{ if (freq_height[j] <= 5 && freq_height[j] > 2){freq_array[j] = 1;}
    else{ if (freq_height[j] <= 2 && freq_height[j] > 0){freq_array[j] = 0;}
  }}}}}}}}}}}}}}}}}
  
    String sta = "M";
    String aa = str(freq_array[0]);
    String bb = str(freq_array[1]);
    String cc = str(freq_array[2]);
    String dd = str(freq_array[3]);
    String ee = str(freq_array[4]);
    String ff = str(freq_array[5]);
    String gg = str(freq_array[6]);
    String hh = str(freq_array[7]);
    String ii = str(freq_array[8]);
    String jj = str(freq_array[9]);
    String kk = str(freq_array[10]);
    String ll = str(freq_array[11]);
    String mm = str(freq_array[12]);
    String nn = str(freq_array[13]);
    String oo = str(freq_array[14]);
    String pp = str(freq_array[15]);
    String qq = str(freq_array[16]);
    String rr = str(freq_array[17]);
    String ss = str(freq_array[18]);
    String tt = str(freq_array[19]);
    String uu = str(freq_array[20]);
    String vv = str(freq_array[21]);
    String ww = str(freq_array[22]);
    String xx = str(freq_array[23]);
    String yy = str(freq_array[24]);
    String zz = str(freq_array[25]);
    String aaa = str(freq_array[26]);
    String bbb = str(freq_array[27]);
    String ccc = str(freq_array[28]);
    String ddd = str(freq_array[28]);
    String eee = str(freq_array[30]);
    String fff = str(freq_array[31]);
    String ggg = str(freq_array[32]);
    String hhh = str(freq_array[33]);
    String iii = str(freq_array[34]);
    String jjj = str(freq_array[35]);
    String kkk = str(freq_array[36]);
    String lll = str(freq_array[37]);
    String mmm = str(freq_array[38]);
    String nnn = str(freq_array[39]);
    String ooo = str(freq_array[40]);
    String ppp = str(freq_array[41]);
    String qqq = str(freq_array[42]);
    String rrr = str(freq_array[43]);
    String sss = str(freq_array[44]);
    String ttt = str(freq_array[45]);
    String uuu = str(freq_array[46]);
    String vvv = str(freq_array[47]);
    String www = str(freq_array[48]);
    String xxx = str(freq_array[49]);
    String yyy = str(freq_array[50]);
    String zzz = str(freq_array[51]);
    String aaaa = str(freq_array[52]);
    String bbbb = str(freq_array[53]);
    String cccc = str(freq_array[54]);
    String dddd = str(freq_array[55]);
    String eeee = str(freq_array[56]);
    String ffff = str(freq_array[57]);
    String gggg = str(freq_array[58]);
    String hhhh = str(freq_array[59]);
    String iiii = str(freq_array[60]);
    String jjjj = str(freq_array[61]);
    String kkkk = str(freq_array[62]);
    String llll = str(freq_array[63]);
    String com = ",";
    String newl = "\n";
    String send1 = sta + com + aa + com + bb + com + cc + com + dd + com + ee + com + ff + com + gg + com + hh + com + ii + com + jj + com + kk + com + ll + com + mm + com + nn + com + oo + com + pp + com + qq + com + rr + com + ss + com + tt + com + uu + com + vv + com + ww + com + xx + com + yy + com + zz + com + aaa + com + bbb + com + ccc + com + ddd + com + eee + com + fff + com + ggg + com + hhh + com + iii + com+ jjj + com + kkk + com + lll + com + mmm + com + nnn + com + ooo + com + ppp + com + qqq + com + rrr + com + sss + com + ttt + com + uuu + com + vvv + com + www + com + xxx + com + yyy + com + zzz + com + aaaa + com + bbbb + com + cccc + com + dddd + com + eeee + com + ffff + com + gggg + com + hhhh + com + iiii + com + jjjj + com + kkkk + com + llll + newl;
    port1.write(send1);
    port1.clear();
}

void stop()
{
  in.close();
  minim.stop();
  super.stop();
}

Continued below:

And the arduino side:

#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Max72xxPanel.h>

int pinCS = 10;
int numberOfHorizontalDisplays = 8;
int numberOfVerticalDisplays = 2;
int amplitude[64] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
boolean Cleared = true;
Max72xxPanel matrix = Max72xxPanel(pinCS, numberOfHorizontalDisplays, numberOfVerticalDisplays);

void setup() {

matrix.fillScreen(LOW);
matrix.write();
matrix.setIntensity(15);

matrix.setRotation(0, 1);   
matrix.setRotation(1, 1);    
matrix.setRotation(2, 1);    
matrix.setRotation(3, 1);    
matrix.setRotation(4, 1);    
matrix.setRotation(5, 1);    
matrix.setRotation(6, 1);    
matrix.setRotation(7, 1);   
matrix.setRotation(8, 1);    
matrix.setRotation(9, 1);
matrix.setRotation(10, 1);
matrix.setRotation(11, 1);
matrix.setRotation(12, 1);
matrix.setRotation(13, 1);
matrix.setRotation(14, 1);
matrix.setRotation(15, 1); 
Serial.begin(115200);
delay(1000);
}

void loop() { 
  
  if(Serial.read() == ('M')){
    for(int j=0; j<64; j++){
    amplitude[j]=Serial.parseInt();    
    }
   }  
  
  if(Serial.read()=='\n'){
    matrix.fillScreen(LOW);
    for (int x = 0; x<matrix.width(); x++){
       int temp = amplitude[x];
       matrix.drawLine(x, 16, x, 16 - temp, HIGH);
     }      
    matrix.write();
    for (int a = 0; a<64; a++){
      amplitude[a] = 0;
    }
   } 
}

I understand that this can be improved by leaps and bounds but I was using this to prototype the system and have run into a problem. If I constraint the freq_height array values to a range of 0 - 8 by reducing the resolution, everything seems to work fine. Alternatively if I reduce the volume on the laptop thereby reducing the overall range of the values in the array things seem to work fine as well.

However as soon as I try to use the full resolution of 16 on max volume such that the freq_array has multiple double digit values the system simply hangs. I can see the last displayed frame on the matrix and that's it. I tried to use the BT module with SoftwareSerial (I do not have a Mega ATM) at a much lower baud rate and can see that the string reaches the Arduino. So I believe that I am parsing the string all wrong. I read through this:

https://forum.arduino.cc/index.php?topic=288234.0

but was unable to adapt the code to parse my incoming data (essentially M followed by 64 comma separated values ending in a new line). Although I can not understand why the above works with relatively small values in the freq_array.

I would really appreciate some help on this since I seem to have reached the end of my intellectual capacity.

Thanks
Nitin

Yes the code could be very greatly improved it is very turged.

The problem on the Arduino side is that if you read an M from the serial port you then assume that their are 64 values to read and they might not all have arrived.

On the processing side you are being very wasteful sending the data as strings and giving the Arduino a lot more to do in converting that string into an int value.

If you only send int values then you cut down one hell of a lot of work the system has to do. Note your Arduino serial buffer is only 64 bytes anyway.

Yes the code could be very greatly improved it is very turged.

Apologies for asking for help on such code but I am trying to get a proof of concept going and will optimize once I can get my head around this.

On the processing side you are being very wasteful sending the data as strings and giving the Arduino a lot more to do in converting that string into an int value.

If you only send int values then you cut down one hell of a lot of work the system has to do. Note your Arduino serial buffer is only 64 bytes anyway.

Super thanks for this. To get to think of it I need only a byte (?)/channel since I will send values from 0 - 16 only. So 64 bytes in all. Could you please let me know if this line of thought is correct? This would fit nicely within the 64 byte serial buffer?

Also on the processing side if I change the freq_array to byte, is there a way to send the byte array in a single go or would I need to run a loop to send the individual values?

As always much appreciate the help!!

Thanks
Nitin

Could you please let me know if this line of thought is correct? This would fit nicely within the 64 byte serial buffer?

Yes this is the right lines.

With processing you always need to run through a loop to send an array.

As an update to this, as suggested by Mike, I updated my Arduino code to the following:

#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Max72xxPanel.h>

int pinCS = 10; // Attach CS to this pin, DIN to MOSI and CLK to SCK (cf http://arduino.cc/en/Reference/SPI )
int numberOfHorizontalDisplays = 8;
int numberOfVerticalDisplays = 2;

const byte numBytes = 64;
byte receivedBytes[numBytes] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
boolean newData = false;

Max72xxPanel matrix = Max72xxPanel(pinCS, numberOfHorizontalDisplays, numberOfVerticalDisplays);

void setup() {

matrix.fillScreen(LOW);
matrix.write();
matrix.setIntensity(1);

matrix.setRotation(0, 1);    // The first display is position upside down
matrix.setRotation(1, 1);    
matrix.setRotation(2, 1);    
matrix.setRotation(3, 1);    
matrix.setRotation(4, 1);    
matrix.setRotation(5, 1);    
matrix.setRotation(6, 1);    
matrix.setRotation(7, 1);   
matrix.setRotation(8, 1);    
matrix.setRotation(9, 1);
matrix.setRotation(10, 1);
matrix.setRotation(11, 1);
matrix.setRotation(12, 1);
matrix.setRotation(13, 1);
matrix.setRotation(14, 1);
matrix.setRotation(15, 1);   // The same hold for the last display 
Serial.begin(19200);
delay(1000);
 }

void recvWithStartEndMarkers() {
    static boolean recvInProgress = false;
    static byte ndx = 0;
    char startMarker = '<';
    char endMarker = '>';
    byte rc;
 
 if (Serial.available() > 0) {
    while (Serial.available() > 0 && newData == false) {
        rc = Serial.read();

        if (recvInProgress == true) {
            if (rc != endMarker) {
                receivedBytes[ndx] = rc;
                ndx++;
                /*if (ndx >= numBytes) {
                    ndx = numBytes - 1;
                }*/
            }
            else {
                //receivedBytes[ndx] = '\0'; // terminate the string
                recvInProgress = false;
                ndx = 0;
                newData = true;
            }
        }
      else if (rc == startMarker) {
            recvInProgress = true;
        }
    }
  }
}
void showNewData() {
    if (newData == true) {     
        matrix.fillScreen(LOW);
         //matrix.write(); 
        for (int b = 0; b<matrix.width(); b++){
         int temp = int (receivedBytes[b]);
         int temp1 = min(16, temp);
         matrix.drawLine(b, 16, b, 16 - temp, HIGH);
         }
         matrix.write();
         
         for (int a = 0; a<64; a++){
          receivedBytes[a] = 0;
          }
        newData = false;
    
    }
   
}

void loop() { // run over and over
    recvWithStartEndMarkers();
    showNewData();  
  }

This is all thanks to:

https://forum.arduino.cc/index.php?topic=288234.0

In order to simply test the system I used a processing sketch:

import processing.serial.*;
import ddf.minim.analysis.*;
import ddf.minim.*;

Minim minim;
AudioInput in;
FFT fft;
int buffer_size = 4096; 
float sample_rate = 200000;
int freq_width = 150;
int bands = 64;
Serial port1;
float[] spectrum = new float[bands];
byte []freq_array = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; 
float[] freq_height = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
char start = '<';
char end = '>';

void settings()
{
 size(512, 360);
}
void setup() {
  background(255); 
  port1 = new Serial (this, "COM7", 19200);
}      

void draw() { 
  background(255);
  
  for(int j=0; j<64; j++){    
  freq_array[j] = byte(random(0,17));
  }
 port1.write(start);
 port1.write(freq_array);
 port1.write(end);
}

void stop()
{
  in.close();
  minim.stop();
  super.stop();
}

Now, the original idea was to have this working wireless with an HC05 module. But to first validate the system I simply used an FTDI basic board to hook up the standalone arduino to the computer running the processing sketch.

The problem that I am currently facing is that this seems to work just fine at 19200 baud but the display is laggy (essentially out of sync with the music). If I use a higher baud, say 38400, the display simply hangs at random (the arduino side) even when running the above processing test sketch.

However, adding a small delay at the end of port1.write(end) (40ms was the minimum I could go to), the system seems to work fine. While this solution somewhat works, I am wondering if someone could help me understand this behavior and if there is a way around this.

Thanks
Nitin

You have nothing to stop the byte data being equal to the stop or start marker. So I would expect it to jam up at any speed.

Grumpy_Mike:
You have nothing to stop the byte data being equal to the stop or start marker. So I would expect it to jam up at any speed.

Thanks for the quick reply Mike. I apologize if I did not understand your comment correctly. You are saying that if the data being sent by the processing serial at some point becomes equal to either the start/stop markers unintentionally then the sketch on the arduino would misinterpret this as the start or the end of the data stream?

A quick follow up on this though, if my above understanding is correct then the ASCII value of the start and the stop markers, 60 and 62 respectively would be the cause of the problem? The reason that I ask this is because the data that I send only ever has values between 0 - 16 from this line:

freq_array[j] = byte(random(0,17));

Again I really appreciate you taking the time to guide me.

Thanks
Nitin

he reason that I ask this is because the data that I send only ever has values between 0 - 16 from this line:

Sorry I missed that bit, that will prevent the data being confused with the the sart bits.

However note that in Processing the draw function is not quite like the loop function as it only gets called 25 times a second not as fast as possible like the loop function.

Try timing how long it takes your Arduino to read a frame. Sending data faster and it it working does not make sense unless it is taking too long to do the stuff on the Arduino side and the input buffer is overflowing.

Grumpy_Mike:
However note that in Processing the draw function is not quite like the loop function as it only gets called 25 times a second not as fast as possible like the loop function.

Try timing how long it takes your Arduino to read a frame. Sending data faster and it it working does not make sense unless it is taking too long to do the stuff on the Arduino side and the input buffer is overflowing.

Ah! I did not know that about processing. Thanks for the pointer. I had a feeling that probably the Arduino is taking longer than the send speed to display the data. Incidentally in the processing sketch if I limit the values to 0 - 8, the sketch seems to run fine even at 115200.

I wish I had access to a scope or a logic analyzer to measure the timing. I am not sure if there is another approach to do this. Thanks for the help though I will try fiddling around with the baud rate and the delay till then.

Incidentally in the processing sketch if I limit the values to 0 - 8, the sketch seems to run fine even at 115200.

That points at the display part taking too long before the next lot of data comes along.

You can slow down the rate that Processing kicks out the data by using the frameRate call:-

Seems like the default rate is now 60 times a second not 25 like it used to be.

Grumpy_Mike:
That points at the display part taking too long before the next lot of data comes along.

You can slow down the rate that Processing kicks out the data by using the frameRate call:-
frameRate() / Reference / Processing.org

Seems like the default rate is now 60 times a second not 25 like it used to be.

Awesome! Thanks Mike.

Finally with the fantastic help from Mike I have some semblance of a working prototype in place. Video at (apologies for the bad quality of the video):

Look forward to many such projects.

Thanks
Nitin

I have been it itching to do more on this, mainly because it looks so cool. I was thinking of expanding this both horizontally and vertically. So probably from 64 to 128 channels and then from 16 rows to 32 rows.

But given the timing issues this naturally can not be done with a single ATMEGA. I am curious to understand if it is possible to use the same HC-05 and have its output physically go to 2 separate stand alone arduinos and then use the appropriate values from the output in software.

So basically in the processing sketch send over 128 values and use one arduino to drive the first 64 columns and the second one to drive the next 64 but with a single blue tooth module.

Would certainly appreciate any thoughts.

Thanks
Nitin

I am curious to understand if it is possible to use the same HC-05 and have its output physically go to 2 separate stand alone arduinos

If the data is one way only then yes, but I think it would involve some sort of acknowledgment signal and you can't have two Arduinos doing that. It would be better to receive all the data with one Arduino and have it pass on half that to the other Arduino.

However, two Arduinos is seldom the answer. As you have a Max7219 in the mix that is doing the grunt work in regard to multiplexing so their is nothing much holding you back from using just one. Are you chaining the Max7219s?

Thanks for the reply Mike. The data would only be one way since the way this is currently set up, the arduino does not send back a confirmation to the processing sketch doing the FFT to indicate the completion of the display of a frame.

I did some initial test to try to send data from the HC 05 to the PC over the weekend but that did not work. I will probably need more time to set it up properly.

I am using Max7219 based modules, but the problem in its current shape and form was that when sending data at >38400 baud with no acknowledgement/delay between transmissions, the arduino just hangs after a while. This happens specifically when the values from the FFT are large (substantial number of values >8). If i limit the values being sent by processing to 0 - 8 things work just fine even without a delay. If the arduino is taking too long to load the values to the 7219 and the new data set arrives before that, I suppose at some point the sketch on the arduino would lock up.

I currently have a delay of 40 milliseconds before transmissions on the processing and that seems to work well. If i increase the delay the display looks "out of sync" with the music, hence the thought of multiple arduinos.

Thanks for all the guidance.

Hi all i using Max72xxPanel library on my project, i am using visual studio code editor, copied both .h and.cpp file on my include folder, i got following error, unable to guess what cause this error?, how to resolve it?

/home/raman/.platformio/packages/toolchain-xtensa/bin/../lib/gcc/xtensa-lx106-elf/4.8.2/../../../../xtensa-lx106-elf/bin/ld: .pio/build/esp32dev/src/main.cpp.o:(.text.startup._GLOBAL__sub_I_pinCS+0x20): undefined reference to Max72xxPanel::Max72xxPanel(unsigned char, unsigned char, unsigned char)' /home/raman/.platformio/packages/toolchain-xtensa/bin/../lib/gcc/xtensa-lx106-elf/4.8.2/../../../../xtensa-lx106-elf/bin/ld: .pio/build/esp32dev/src/main.cpp.o: in function _GLOBAL__sub_I_pinCS':
main.cpp:(.text.startup._GLOBAL__sub_I_pinCS+0x50): undefined reference to Max72xxPanel::Max72xxPanel(unsigned char, unsigned char, unsigned char)' /home/raman/.platformio/packages/toolchain-xtensa/bin/../lib/gcc/xtensa-lx106-elf/4.8.2/../../../../xtensa-lx106-elf/bin/ld: .pio/build/esp32dev/src/main.cpp.o:(.text.setup+0x1c): undefined reference to Max72xxPanel::setIntensity(unsigned char)'
/home/raman/.platformio/packages/toolchain-xtensa/bin/../lib/gcc/xtensa-lx106-elf/4.8.2/../../../../xtensa-lx106-elf/bin/ld: .pio/build/esp32dev/src/main.cpp.o:(.text.setup+0x20): undefined reference to Max72xxPanel::setRotation(unsigned char, unsigned char)' /home/raman/.platformio/packages/toolchain-xtensa/bin/../lib/gcc/xtensa-lx106-elf/4.8.2/../../../../xtensa-lx106-elf/bin/ld: .pio/build/esp32dev/src/main.cpp.o:(.text.setup+0x24): undefined reference to Max72xxPanel::fillScreen(unsigned short)'
/home/raman/.platformio/packages/toolchain-xtensa/bin/../lib/gcc/xtensa-lx106-elf/4.8.2/../../../../xtensa-lx106-elf/bin/ld: .pio/build/esp32dev/src/main.cpp.o:(.text.setup+0x28): undefined reference to Max72xxPanel::write()' /home/raman/.platformio/packages/toolchain-xtensa/bin/../lib/gcc/xtensa-lx106-elf/4.8.2/../../../../xtensa-lx106-elf/bin/ld: .pio/build/esp32dev/src/main.cpp.o: in function setup':
main.cpp:(.text.setup+0x74): undefined reference to Max72xxPanel::setIntensity(unsigned char)' /home/raman/.platformio/packages/toolchain-xtensa/bin/../lib/gcc/xtensa-lx106-elf/4.8.2/../../../../xtensa-lx106-elf/bin/ld: main.cpp:(.text.setup+0x80): undefined reference to Max72xxPanel::setRotation(unsigned char, unsigned char)'
/home/raman/.platformio/packages/toolchain-xtensa/bin/../lib/gcc/xtensa-lx106-elf/4.8.2/../../../../xtensa-lx106-elf/bin/ld: main.cpp:(.text.setup+0x8c): undefined reference to Max72xxPanel::setRotation(unsigned char, unsigned char)' /home/raman/.platformio/packages/toolchain-xtensa/bin/../lib/gcc/xtensa-lx106-elf/4.8.2/../../../../xtensa-lx106-elf/bin/ld: main.cpp:(.text.setup+0x98): undefined reference to Max72xxPanel::setRotation(unsigned char, unsigned char)'
/home/raman/.platformio/packages/toolchain-xtensa/bin/../lib/gcc/xtensa-lx106-elf/4.8.2/../../../../xtensa-lx106-elf/bin/ld: main.cpp:(.text.setup+0xa4): undefined reference to Max72xxPanel::setRotation(unsigned char, unsigned char)' /home/raman/.platformio/packages/toolchain-xtensa/bin/../lib/gcc/xtensa-lx106-elf/4.8.2/../../../../xtensa-lx106-elf/bin/ld: main.cpp:(.text.setup+0xae): undefined reference to Max72xxPanel::fillScreen(unsigned short)'
/home/raman/.platformio/packages/toolchain-xtensa/bin/../lib/gcc/xtensa-lx106-elf/4.8.2/../../../../xtensa-lx106-elf/bin/ld: main.cpp:(.text.setup+0xb8): undefined reference to Max72xxPanel::write()' /home/raman/.platformio/packages/toolchain-xtensa/bin/../lib/gcc/xtensa-lx106-elf/4.8.2/../../../../xtensa-lx106-elf/bin/ld: main.cpp:(.text.setup+0x11c): undefined reference to Max72xxPanel::write()'
/home/raman/.platformio/packages/toolchain-xtensa/bin/../lib/gcc/xtensa-lx106-elf/4.8.2/../../../../xtensa-lx106-elf/bin/ld: main.cpp:(.text.setup+0x12c): undefined reference to Max72xxPanel::fillScreen(unsigned short)' /home/raman/.platformio/packages/toolchain-xtensa/bin/../lib/gcc/xtensa-lx106-elf/4.8.2/../../../../xtensa-lx106-elf/bin/ld: main.cpp:(.text.setup+0x134): undefined reference to Max72xxPanel::write()'
/home/raman/.platformio/packages/toolchain-xtensa/bin/../lib/gcc/xtensa-lx106-elf/4.8.2/../../../../xtensa-lx106-elf/bin/ld: .pio/build/esp32dev/src/main.cpp.o:(.text.loop+0x39): undefined reference to Max72xxPanel::setIntensity(unsigned char)' /home/raman/.platformio/packages/toolchain-xtensa/bin/../lib/gcc/xtensa-lx106-elf/4.8.2/../../../../xtensa-lx106-elf/bin/ld: .pio/build/esp32dev/src/main.cpp.o:(.text.loop+0x43): undefined reference to Max72xxPanel::fillScreen(unsigned short)'
/home/raman/.platformio/packages/toolchain-xtensa/bin/../lib/gcc/xtensa-lx106-elf/4.8.2/../../../../xtensa-lx106-elf/bin/ld: .pio/build/esp32dev/src/main.cpp.o: in function loop': main.cpp:(.text.loop+0xe2): undefined reference to Max72xxPanel::write()'
collect2: error: ld returned 1 exit status
*** [.pio/build/esp32dev/firmware.elf] Error 1