Adafruit RGBLCD Shield with Particle Photon (port from Arduino)

I started a support request on the Adafruit forum and we can’t figure out what is going on. I had a working Arduino project that I am trying to port to a Particle Photon with a Shield Shield. I have worked through several issues but I can’t compile the code due to a library issue.

Basically, I am getting multiple definition errors when using the “Adafruit_RGBLCDShield” library on the Particle Web IDE. Here is my original support request in its entirety:

Original Support Request on Adafruit Forum

I saw some other posts here (Particle Support Forum) that reference the Adafruit RGBLCD but they seem to not have issues compiling the library:

Adafruit Sous Viduino

User @peekay123 updated the AdafruitRGBLCD Library in 2014, but what is posted on the Github does not match what I see when I view the libraries on the Web IDE. Maybe that is the root of the problem?

pkourany/Adafruit_RGBLCDShield_Library

See next post for a copy of my code but note that the same thing happens when using the Hello World example.

Error code:

../../../build/target/user/platform-6/libuser.a(Adafruit_MCP23017.o): In function `__static_initialization_and_destruction_0':
Adafruit_RGBLCDShield/Adafruit_MCP23017.cpp:41: multiple definition of `Adafruit_MCP23017::begin(unsigned char)'
../../../build/target/user/platform-6/libuser.a(Adafruit_MCP23017.o):Adafruit_MCP23017/Adafruit_MCP23017.cpp:110: first defined here
../../../build/target/user/platform-6/libuser.a(Adafruit_MCP23017.o): In function `Adafruit_MCP23017::begin()':
Adafruit_RGBLCDShield/Adafruit_MCP23017.cpp:64: multiple definition of `Adafruit_MCP23017::begin()'
../../../build/target/user/platform-6/libuser.a(Adafruit_MCP23017.o):Adafruit_MCP23017/Adafruit_MCP23017.cpp:128: first defined here
../../../build/target/user/platform-6/libuser.a(Adafruit_MCP23017.o): In function `Adafruit_MCP23017::pinMode(unsigned char, unsigned char)':
Adafruit_RGBLCDShield/Adafruit_MCP23017.cpp:72: multiple definition of `Adafruit_MCP23017::pinMode(unsigned char, unsigned char)'
../../../build/target/user/platform-6/libuser.a(Adafruit_MCP23017.o):Adafruit_MCP23017/Adafruit_MCP23017.cpp:134: first defined here
../../../build/target/user/platform-6/libuser.a(Adafruit_MCP23017.o): In function `Adafruit_MCP23017::readGPIOAB()':
Adafruit_RGBLCDShield/Adafruit_MCP23017.cpp:104: multiple definition of `Adafruit_MCP23017::readGPIOAB()'
../../../build/target/user/platform-6/libuser.a(Adafruit_MCP23017.o):Adafruit_MCP23017/Adafruit_MCP23017.cpp:141: first defined here
../../../build/target/user/platform-6/libuser.a(Adafruit_MCP23017.o): In function `Adafruit_MCP23017::writeGPIOAB(unsigned short)':
Adafruit_RGBLCDShield/Adafruit_MCP23017.cpp:122: multiple definition of `Adafruit_MCP23017::writeGPIOAB(unsigned short)'
../../../build/target/user/platform-6/libuser.a(Adafruit_MCP23017.o):Adafruit_MCP23017/Adafruit_MCP23017.cpp:181: first defined here
../../../build/target/user/platform-6/libuser.a(Adafruit_MCP23017.o): In function `Adafruit_MCP23017::digitalWrite(unsigned char, unsigned char)':
Adafruit_RGBLCDShield/Adafruit_MCP23017.cpp:135: multiple definition of `Adafruit_MCP23017::digitalWrite(unsigned char, unsigned char)'
../../../build/target/user/platform-6/libuser.a(Adafruit_MCP23017.o):Adafruit_MCP23017/Adafruit_MCP23017.cpp:60: first defined here
../../../build/target/user/platform-6/libuser.a(Adafruit_MCP23017.o): In function `Adafruit_MCP23017::pullUp(unsigned char, unsigned char)':
Adafruit_RGBLCDShield/Adafruit_MCP23017.cpp:174: multiple definition of `Adafruit_MCP23017::pullUp(unsigned char, unsigned char)'
../../../build/target/user/platform-6/libuser.a(Adafruit_MCP23017.o):Adafruit_MCP23017/Adafruit_MCP23017.cpp:206: first defined here
../../../build/target/user/platform-6/libuser.a(Adafruit_MCP23017.o): In function `Adafruit_MCP23017::digitalRead(unsigned char)':
Adafruit_RGBLCDShield/Adafruit_MCP23017.cpp:211: multiple definition of `Adafruit_MCP23017::digitalRead(unsigned char)'
../../../build/target/user/platform-6/libuser.a(Adafruit_MCP23017.o):Adafruit_MCP23017/Adafruit_MCP23017.cpp:60: first defined here
collect2: error: ld returned 1 exit status
make: *** [dfa0388819b87ca9ebcf8141b7e161eaf0f8693f1673e73599cdcc6f67d6.elf] Error 1
Error: Could not compile. Please review your code.

Sketch Code:

/*********************

  Fan Controller
  by enginesong
  Version 1 - August 14,2016
  Ported to Particle - September 02, 2016

**********************/
// Included Libraries:

//#include "Adafruit_MCP23017/Adafruit_MCP23017.h"
#include "Adafruit_RGBLCDShield/Adafruit_RGBLCDShield.h"
#include "Adafruit_MAX31855/Adafruit_MAX31855.h"
#include "math.h"

// The shield uses the I2C SCL and SDA pins. On classic Arduinos
// this is Analog 4 and 5 so you can't use those for analogRead() anymore
// However, you can connect other I2C sensors to the I2C bus and share
// the I2C bus.
Adafruit_RGBLCDShield lcd = Adafruit_RGBLCDShield();

// These #defines make it easy to set the backlight color
#define OFF 0x0
#define RED 0x1
#define GREEN 0x2
#define YELLOW 0x3
#define BLUE 0x4
#define VIOLET 0x5
#define TEAL 0x6
#define WHITE 0x7

// Create a thermocouple instance with software SPI on any three digital IO pins.
#define MAXDO   3 //connect DO to Particle digital pin 3
#define MAXCS   4 //connect CS to Particle digital pin 4
#define MAXCLK  5 //connect CLK to Particle digital pin 5

// initialize the Thermocouple
Adafruit_MAX31855 thermocouple(MAXCLK, MAXCS, MAXDO);

////////////////////////////////////////////////////////////////////////////////////////
//int set_point[] = {90,250,500}; //set points for: Fan ON, Add wood, Over temp
int set_point[] = {75, 84, 90}; //test set points for: Fan ON, Add wood, Over temp

/*
--------------------------
EEPROM Addresses
--------------------------
0 - FAN on set point.
1 - Add wood/end of warm up set point.
2 - Over temp warning set point.
*/

////////////////////////////////////////////////////////////////////////////////////////

// constants won't change. They're used here to set pin numbers:
const int buttonPin = 2;    // the number of the pushbutton pin
const int relayPin = 6;    // the number of the relay pin
const int ledPin = 7;      // the number of the LED pin

// Variables will change:
uint8_t ledState = LOW;         // the current state of the LED output pin
uint8_t relayState = LOW;       // the current state of the relay output pin
uint8_t buttonState;            // the current reading from the button input pin
//int lastButtonState = LOW;  // the previous reading from the input pin
uint8_t lastButtonState = LOW;  // the previous reading from the input pin
//unsigned int reading = 0;
uint8_t auto_mode = LOW;       // default is auto mode ON
uint8_t lastLedState = LOW;     // the previous reading from the relay
uint8_t cooling_down = LOW;     // initially will be warming up, not cooling down

// Generally, you should use "unsigned long" for variables that hold time
// The value will quickly become too large for an int to store
unsigned long previousMillis = 0;        // will store last time LED was updated

// constants won't change :
const long interval = 10000;           // time out interval (milliseconds). Used for pumps, etc.

// the following variables are long's because the time, measured in miliseconds,
// will quickly become a bigger number than can be stored in an int.
//need to be unsigned to prevent comparison errors with millis()
unsigned long lastDebounceTime = 0;  // the last time the output pin was toggled
unsigned long lastDebounceTime2 = 0;  // the last time the output pin was toggled

//long debounceDelay = 50;    // the debounce time; increase if the output flickers
unsigned long debounceDelay = 150;    // the debounce time; increase if the output flickers, requires button hold down for minimum 150ms.  Toggles Manual Mode.
unsigned long debounceDelay2 = 2000;    // the debounce time; increase if the output flickers, requires LED change commanded for longer than this time. Toggles Relay.
unsigned long debounceDelay3 = 10000;  // the debounce time; increase if the output flickers, requires relay change commanded for longer than this time. Determines warming up or cooling down.

void setup() {
  // set up the LCD's number of columns and rows:
  lcd.begin(16, 2);
  lcd.clear();
  lcd.setBacklight(BLUE);

  // define pin types
  pinMode(buttonPin, INPUT);
  pinMode(ledPin, OUTPUT);
  pinMode(relayPin, OUTPUT);

  // set initial LED and relay states
  digitalWrite(ledPin, ledState);
  digitalWrite(relayPin, relayState);

}

void loop() {
  // read the state of the switch into a local variable:
  uint8_t reading = digitalRead(buttonPin);

  // check to see if you just pressed the button
  // (i.e. the input went from LOW to HIGH),  and you've waited
  // long enough since the last press to ignore any noise:

  // If the button changed, due to noise or command, reset debouncing timer:
  if (reading != lastButtonState) {
      lastDebounceTime = millis();  //LED timer
  }

  //if the following is true, the button has been pressed ->toggle manual mode
  if ((millis() - lastDebounceTime) > debounceDelay) {
    // whatever the reading is at, it's been there for longer
    // than the debounce delay, so take it as the actual current state:

    // if the button state has changed:
    if (reading != buttonState) {
      buttonState = reading;

      // toggle the LED if the new button state is HIGH, auto mode is ON and LED is OFF
      if ((buttonState == HIGH) && (auto_mode == HIGH) && (ledState == LOW)) {
        ledState = !ledState;
        //relayState = !relayState;
        auto_mode = !auto_mode;  //toggle auto mode if button is pressed
        lcd.clear();
        debounceDelay2 = 1000; //shorter relay change delay due to manual button press
      }
      // toggle the LED if the new button state is HIGH, auto mode is OFF and LED is ON
      else if ((buttonState == HIGH) && (auto_mode == LOW) && (ledState == HIGH)) {
        ledState = !ledState;
        auto_mode = !auto_mode;  //toggle auto mode if button is pressed
        lcd.clear();
        lcd.setCursor(0, 0);
        lcd.print(" Auto Mode: ON");
        delay(5000);
        lcd.clear();
      }
      // Do not toggle the LED if the new button state is HIGH, auto mode is ON and LED is ON
      else if ((buttonState == HIGH) && (auto_mode == HIGH) && (ledState == HIGH)) {
        lcd.clear();
        lcd.setCursor(0, 0);
        lcd.print(" Fan Already ON");
        lcd.setCursor(0, 1);
        lcd.print("Button Disabled!");
        delay(5000);
        lcd.clear();
      }
      // re-enable auto mode if the new button state is HIGH, auto mode is OFF and LED is OFF
      else if ((buttonState == HIGH) && (auto_mode == LOW) && (ledState == LOW)) {
        auto_mode = HIGH;  //toggle auto mode if button is pressed
        lcd.clear();
      }
    }
    // set the LED:
    if (!auto_mode) {
      digitalWrite(ledPin, ledState); //relay follows
      //digitalWrite(relayPin, relayState);
    }
  }

  // Change display when in manual mode
  if (!auto_mode) {
    lcd.setBacklight(VIOLET);   //manual mode active
    lcd.setCursor(0, 0);
    lcd.print(" Auto Mode: OFF");
    lcd.setCursor(0, 1);
    lcd.print("     Fan ON");
  }

  //normal automatic mode (if auto_mode is true)
  if (auto_mode) {
    debounceDelay2 = 10000;  //longer relay debounce delay due to temperature change
    //uint8_t buttons = lcd.readButtons();
    double f = thermocouple.readFarenheit();
    lcd.setCursor(0, 0);
    if (isnan(f)) {
      lcd.print("T/C Problem");
    }
    else {
      if (f < 100.0) {  //checks if 2 digits or 3 digits to report for display spacing
        lcd.setCursor(4, 1); //set cursor to start of second row
        lcd.print(" ");
        lcd.setCursor(5, 1);
        lcd.print(f, 0); //zero significant digits to the right of the decimal, no decimal either
      }
      else {
        lcd.setCursor(4, 1);
        lcd.print(f, 0);
      }
      lcd.setCursor(7, 1);
      lcd.print(" degF");
    }
    //add millis timer(s) to make sure face color or switches don't change too often as temp passes through set points
    if (f <= set_point[0]) {
      ledState = LOW;
      digitalWrite(ledPin, ledState);  //turn ON LED, relay follows
      lcd.setCursor(0, 0); //set cursor to start of top row
      lcd.print("Fan Control OFF");
      lcd.setBacklight(BLUE);  //if temp is below normal range.
    } //end if Relay is OFF arguements
    else {
      ledState = HIGH;
      digitalWrite(ledPin, ledState);  // turn ON LED, relay follows
      if (f >= set_point[2]) {
        //"Over Temp" warning Output here
        lcd.setCursor(0, 0); //set cursor to start of top row
        lcd.print(" !!!WARNING!!! ");
        lcd.setBacklight(RED);  //if temp is above normal range. High Temp trigger.
      }
      else if (f > set_point[1]) {
        //No special notes for "Normal"
        lcd.setCursor(0, 0); //set cursor to start of top row
        lcd.print("  Normal Range  ");
        lcd.setBacklight(GREEN);  //if temp is within normal range.
      }
      else {
        lcd.setBacklight(WHITE); //if temp is within normal range but warming up or add wood.
        lcd.setCursor(0, 0); //set cursor to start of top row
        if (cooling_down == LOW) {
          lcd.print("   Warming Up   ");          
        }
        else {
          //"Add wood" Output here
          lcd.print(" Add More Wood! ");
        }  
      }
    } //end if Relay is ON arguements

  } //end if "auto_mode"
  
  uint8_t buttons = lcd.readButtons();
 
   if (buttons) {
     int menu_cursor1 = 0;
     lcd.setBacklight(WHITE);
     lcd.clear();
     lcd.setCursor(0,0);
     lcd.print(" Setpoint Menu  ");
     delay(2000);
     lcd.setCursor(0,1);
     lcd.print("  -Directions-  ");
     delay(2000);
     lcd.setCursor(0,1);
     lcd.print("LEFT: Back/Exits");
     delay(2000);
     lcd.setCursor(0,1);
     lcd.print("UP/DOWN: scrolls");
     delay(2000);
     lcd.setCursor(0,1);
     lcd.print("SELECT: selects ");
     delay(2000);
     while (1 > 0) {
       buttons = lcd.readButtons();
       if (buttons & BUTTON_LEFT) {
         break; //only can exit while loop with a LEFT button press
       }
       //lcd.clear();
       lcd.setCursor(0,0);
       lcd.print("Select Setpoint ");
       if (menu_cursor1 == 0) {
         lcd.setCursor(0,1);
         lcd.print(" -FAN turns on- ");
       }
       if (menu_cursor1 == 1) {
         lcd.setCursor(0,1);
         lcd.print(" -Add more wood-");
       }
       if (menu_cursor1 == 2) {
         lcd.setCursor(0,1);
         lcd.print("-Over temp warn-");
       }
       if (buttons & BUTTON_UP) {
         menu_cursor1 = menu_cursor1 - 1;
         if (menu_cursor1 >= 2) {
           menu_cursor1 = 2; 
         }
       }
       if (buttons & BUTTON_DOWN) {
         menu_cursor1 = menu_cursor1 + 1;
         if (menu_cursor1 <= 0) {
           menu_cursor1 = 0; 
         }
       }
       if ((buttons & BUTTON_SELECT) || (buttons & BUTTON_RIGHT)) {  //either selection
         lcd.clear();
         int setpoint0 = EEPROM.read(0);
         int setpoint0_new = setpoint0;
         int setpoint1 = EEPROM.read(1);
         int setpoint1_new = setpoint1;
         int setpoint2 = EEPROM.read(2);
         int setpoint2_new = setpoint2;
         while (1 > 0) {
           buttons = lcd.readButtons();
           if (buttons & BUTTON_LEFT) {
             break; //only can exit while loop with a LEFT button press
           }  
           if (menu_cursor1 == 0) {
            //lcd.clear();
            lcd.setCursor(0,0);
            lcd.print("Fan ON at:");
            lcd.setCursor(11,0);
            lcd.print(setpoint0);
            lcd.setCursor(15,0);
            lcd.print("F");
            lcd.setCursor(0,1);
            lcd.print("New Setpoint    ");
            lcd.setCursor(13,1);
            lcd.print(setpoint0_new);
            if (buttons & BUTTON_UP) {
              setpoint0_new = setpoint0_new + 5;
            }
            if (buttons & BUTTON_DOWN) {
              setpoint0_new = setpoint0_new - 5;
            }
            if ((buttons & BUTTON_SELECT) || (buttons & BUTTON_RIGHT)) { //either selection
              //EEPROM.write(0, setpoint0_new); //save change to EEPROM
              EEPROM.put(0, setpoint0_new); //save change to EEPROM
              setpoint0 = setpoint0_new;
              lcd.clear();
              lcd.setCursor(0,0);
              lcd.print("Setpoint Updated");
              lcd.setCursor(0,1);
              lcd.print("Fan ON at:");
              lcd.setCursor(11,1);
              lcd.print(setpoint0);
              lcd.setCursor(15,1);
              lcd.print("F");
              delay(2500);
              break;  //exit while loop after making selection, return to previous menu
            } //end of making new setpoint
          } // fan ON setpoint
          if (menu_cursor1 == 1) {
            //lcd.clear();
            lcd.setCursor(0,0);
            lcd.print("Add Wood:");
            lcd.setCursor(13,0);
            lcd.print(setpoint1);
            lcd.setCursor(15,0);
            lcd.print("F");
            lcd.setCursor(0,1);
            lcd.print("New Setpoint    ");
            lcd.setCursor(13,1);
            lcd.print(setpoint1_new);
            if (buttons & BUTTON_UP) {
              setpoint1_new = setpoint1_new + 5;
            }
            if (buttons & BUTTON_DOWN) {
              setpoint1_new = setpoint1_new - 5;
            }
            if ((buttons & BUTTON_SELECT) || (buttons & BUTTON_RIGHT)) { //either selection
              //EEPROM.write(1, setpoint1_new); //save change to EEPROM
              EEPROM.put(1, setpoint1_new); //save change to EEPROM
              setpoint1 = setpoint1_new;
              lcd.clear();
              lcd.setCursor(0,0);
              lcd.print("Setpoint Updated");
              lcd.setCursor(0,1);
              lcd.print("Add Wood:");
              lcd.setCursor(12,1);
              lcd.print(setpoint1);
              lcd.setCursor(15,1);
              lcd.print("F");
              delay(2500);
              break;  //exit while loop after making selection, return to previous menu
            } //end of making new setpoint
          } // add wood setpoint
          if (menu_cursor1 == 2) {
            //lcd.clear();
            lcd.setCursor(0,0);
            lcd.print("Warning at:");
            lcd.setCursor(12,0);
            lcd.print(setpoint2);
            lcd.setCursor(15,0);
            lcd.print("F");
            lcd.setCursor(0,1);
            lcd.print("New Setpoint    ");
            lcd.setCursor(13,1);
            lcd.print(setpoint2_new);
            if (buttons & BUTTON_UP) {
              setpoint2_new = setpoint2_new + 10;
            }
            if (buttons & BUTTON_DOWN) {
              setpoint2_new = setpoint2_new - 10;
            }
            if ((buttons & BUTTON_SELECT) || (buttons & BUTTON_RIGHT)) { //either selection
              //EEPROM.write(2, setpoint2_new); //save change to EEPROM
              EEPROM.put(2, setpoint2_new); //save change to EEPROM
              setpoint2 = setpoint2_new;
              lcd.clear();
              lcd.setCursor(0,0);
              lcd.print("Setpoint Updated");
              lcd.setCursor(0,1);
              lcd.print("Warning at:");
              lcd.setCursor(12,1);
              lcd.print(setpoint2);
              lcd.setCursor(15,1);
              lcd.print("F");
              delay(2500);
              break;  //exit while loop after making selection, return to previous menu
            } //end of making new setpoint
          } // warning setpoint
        } //while still in setpoint value menu
       } //end of setpoint value
     } //end setpoint select menu
  lcd.clear();
  }  //end buttons/main menu



  // If the LED changed, due to noise or command, reset debouncing timer:
  if (ledState != lastLedState) {
    lastDebounceTime2 = millis(); //Relay timer
  }

  //if the following is true, the LED has been commanded long enough ->toggle relay mode
  if ((millis() - lastDebounceTime2) > debounceDelay2) {
    // whatever the reading is at, it's been there for longer
    // than the debounce delay, so take it as the actual current state:

    // Set relay state to LED state:
    relayState = ledState;
    digitalWrite(relayPin, relayState);
  }

  if ((millis() - lastDebounceTime2) > debounceDelay3) {
    // whatever the reading is at, it's been there for longer
    // than the debounce delay, so take it as the actual current state:
    
    //relayState = ledState;
    //digitalWrite(relayPin, relayState);
    cooling_down = HIGH;  // fan turned ON a long time ago, so must be cooling down
  }
  else {
    cooling_down = LOW;  //fan turned ON recently, so must be warming up
  }

  // save the reading.  Next time through the loop, it will be the lastButtonState:
  lastButtonState = reading;
  lastLedState = ledState;

  //delay(1000);
}

@enginesong, don’t add any Adafruit_MCP23017 library files to your app since these are part of the Adafruit_RGBLCDShield library. I grabbed your code, attached the Adafruit_RGBLCDShield and Adafruit_MAX31855 libraries to the app and compiled without errors. Make sure to chose a Photon as your compile target. :smiley:

I didn’t add any libraries at all - keep in mind I am using “build.particle.io” on the web.

When you say you “grabbed your code, attached the Adafruit_RGBLCDShield and Adafruit_MAX31855 libraries to the app” , does that mean you are using the web build application and added tabs that have the libraries? Or are you compiling in a desktop environment and adding the libraries?

The way I understand compiling on the web is that as long as the #inlcude references libraries that are already available in the web environment, it should work. Am I mistaken?

Here is a screenshot - I am using Chrome on a Windows 7 PC.

@enginesong, to add an existing library to your app, select the library icon on the left hand side of the web IDE:

Then search for each library name (eg Adafruit_RGBLCDShield), select it and “include in APP”, selecting your app to include into. This will add a #include at the top of your app that you can remove since you already have it in the code. Do this for both libraries and try compiling again.

I must have added Adafruit_MCP23017 in the same way at some point even though I commented out the include later?

I started a new app as a copy of the old. I commented out all of the other includes in the body of the code except “math.h” Then, I added the Adafruit_RGBLCDShield and Adafruit_MAX31855 libraries to the app like you described. Now those two includes are at the top of the app (added by the Particle IDE). The code now compiles as it should.

Interesting. So, if you add a library this way and then comment out or delete the #include, the library is still part of the app? How can you see which libraries are included if that is true?

Thanks for your help regardless, but I could easily see this being a recurring issue unless I missed something?

@enginesong, the “attached” libraries are shown below your app’s filename:

To be honest, I don’t really use the IDE anymore and now use CLI, DEV or local toolchain. With the new libraries 2.0 coming soon, using libraries with CLI will be super easy. Particle is working very to improve their development tools.

Ah, I definitely missed that!

Here is screenshot from version 1 (that doesn’t compile):

Clicked the little “X” next to “Adafruit_MCP23017”, said yes to confirm the removal of the library, save, and it compiles no problem.

Thanks again!

1 Like

The imported library files are still present as part of your project (inside a server side project folder).
Adding or removing an include statement doesn’t add or remove files to/from that folder and the compiler/linker will always try to build all files and resolve references.

1 Like

Yes, I learned that the hard way!