Linking error: Multiple definitions in same line error

Linking error: Multiple definitions in same line error

I am rather new to proper C++ and PlatformIO development, so bear with me.

I am developing an ESP32-DEVKIT-V1 system on the Arduino framework which would benefit from private libraries in order to separate funcionalities (IR reading, sensor reading, etc.). As such, I have developed some classes and put them on the lib/ folder. However, I get an error during linking that affects not only my libraries, but other libdeps.

Error example

This is an example of the many linking errors that occur.

C:\...\PlatformIO\Projects\MyProject/.pio/libdeps/esp32doit-devkit-v1/IRremote/src/ir_Pronto.hpp:329: multiple definition of `IRrecv::compensateAndStorePronto(String*, unsigned short)'; .pio\build\esp32doit-devkit-v1\src\main.cpp.o:C:\...\PlatformIO\Projects\MyProject/.pio/libdeps/esp32doit-devkit-v1/IRremote/src/ir_Pronto.hpp:329: first defined here

Project structure

The project structure is as follows:

.
└── Codigo_Raimundao/
    ├── src/
    │   └── main.cpp
    └── lib/
        ├── detector (abstract class definition)/
        │   ├── src/
        │   │   └── detector.hpp
        │   └── library.json
        ├── enemy_detector/
        │   ├── src/
        │   │   ├── enemy_detector.cpp
        │   │   └── enemy_detector.hpp
        │   └── library.json
        └── ...

Code examples

I will provide code examples of both the infrared signal lib and main code, in order.

ir_control

Headerfile

#ifndef INCLUDE_IR_CONTROL_H_
#define INCLUDE_IR_CONTROL_H_

#define DECODE_SONY

#include <enumerators.hpp>
#include <Arduino.h>
#include <IRremote.hpp>

static constexpr uint8_t kIrPin = 17;

class IRControl{
  private:
    RobotState *state;
    Strategy *strat;

  public:
    IRControl(RobotState *p_state, Strategy *p_strat);
    void ExpectIRSignal();
    void DecodeIRSignal();
};

#endif

Implementation (.cpp file)

#include <ir_control.hpp>

static constexpr bool kDebug = true;
static constexpr char* TAG = "IRControl";

IRControl::IRControl(RobotState *p_state, Strategy *p_strat){
  state = p_state;
  strat = p_strat;
  if (kDebug){
    ESP_LOGI(TAG, "Started IRControl with state %d and strategy %d\n", *state, *strat);
    ESP_LOGI(TAG, "Pointer for state: %p\nPointer for strategy: %p\n", state, strat);
  }
    
  pinMode(LED_BUILTIN, OUTPUT);
  IrReceiver.begin(kIrPin, true, LED_BUILTIN); // LED_BUILTIN como feedback de resposta.
  IrReceiver.enableIRIn(); // Linha possivelmente desnecessária.
}

void IRControl::DecodeIRSignal(){
  IrReceiver.resume();
  if (kDebug){
    Serial.print('Received command: ');
    Serial.println(IrReceiver.decodedIRData.command);
  }
  if (*state != RobotState::kStop && *state != RobotState::kRunning 
      && IrReceiver.decodedIRData.command == RobotState::kReady)
    *state = RobotState::kReady;
  
  // Isso deveria ser um switch case?
  if (*state == RobotState::kReady){
    switch (IrReceiver.decodedIRData.command){
      case RobotState::kRunning:
        *state = RobotState::kRunning;
        break;

      case Strategy::kRadarEsq:
        *strat = Strategy::kRadarEsq;
        break;

      case Strategy::kRadarDir:
        *strat = Strategy::kRadarDir;
        break;

      case Strategy::kCurvaAbertaEsq:
        *strat = Strategy::kCurvaAbertaEsq;
        break;
      
      case Strategy::kCurvaAbertaDir:
        *strat = Strategy::kCurvaAbertaDir;
        break;

      case Strategy::kFollowOnly:
        *strat = Strategy::kFollowOnly;
        break;

      case Strategy::kWoodPecker:
        *strat = Strategy::kWoodPecker;
      
      default:
        break;
    }
  }

  if (IrReceiver.decodedIRData.command == RobotState::kStop)
    *state = RobotState::kStop;
  if (kDebug)
    ESP_LOGI(TAG, "Current state: %d\nCurrent strat: %d\n", *state, *strat);
}

void IRControl::ExpectIRSignal(){
  if (IrReceiver.decode())
    DecodeIRSignal();
}

Main code

#include <Arduino.h>
#include <esp_ipc.h>
#include <QTRSensors.h>
#include <freertos/task.h>
#include <freertos/FreeRTOS.h>

#include <motor_control.hpp>
#include <enemy_detector.hpp>
#include <line_detector.hpp>
#include <ir_control.hpp>

// Pinos dos LEDs

constexpr uint8_t kLed1 = 18;
constexpr uint8_t kLed2 = 19;

constexpr bool kDebug = true;
constexpr char* TAG = "Main";

// Protótipos de função

void CoreTaskOne(void *pvParameters);
void CoreTaskZero(void *pvParameters);

void RunStrategy();
void Radar(Direction d);
void CurvaAberta(Direction d);
void Woodpecker();
void Follow();
void LineDetectedProtocol(Direction direction);

// Globais

RobotState state = RobotState::kReady;
Strategy strat = Strategy::kRadarEsq;
uint32_t time_1;

QTRSensorsAnalog qtra((unsigned char[]){kQtr1, kQtr2}, NUM_SENSORS, NUM_SAMPLES_PER_SENSOR);
uint32_t sensor_values[NUM_SENSORS];

EventGroupHandle_t sensor_events;
EventBits_t enemy_bits, line_bit;

MotorControl motor_control = MotorControl();
IRControl ir_control = IRControl(&state, &strat);
EnemyDetector enemy_detector = EnemyDetector(&state, &sensor_events);
LineDetector line_detector = LineDetector(&state, &sensor_events, sensor_values, &qtra);

TaskHandle_t core_0;
TaskHandle_t core_1;

void setup(){
  analogReadResolution(10); // Necessário para o bom funcionamento do QTR.
  // Desligamento de watchdogs para funcionamento das tasks em loop.
  disableCore1WDT();
  disableCore0WDT();
  if (kDebug){
    Serial.begin(115200);
    while (!Serial){;}
    ESP_LOGI(TAG, "Serial Initialized.");
    ESP_LOGI(TAG, "Starting state: %d\nStarting strat: %d\n", state, strat);
  }
  sensor_events = xEventGroupCreate();
  pinMode(kLed1, OUTPUT);
  pinMode(kLed2, OUTPUT);
  line_detector.Calibrate();

  xTaskCreatePinnedToCore(
    CoreTaskOne,
    "CoreTaskOne",
    20000,
    NULL,
    1,
    &core_1,
    1
  );

  xTaskCreatePinnedToCore(
    CoreTaskZero,
    "CoreTaskZero",
    20000,
    NULL,
    1,
    &core_0,
    0
  );
}

void loop(){} 

void CoreTaskOne(void *pvParameters){
  for(;;){
    if (state != RobotState::kStop)
      RunStrategy();
    else{
      motor_control.StopMotors();
      vTaskDelete(core_1);
    }
  }
}

void CoreTaskZero(void *pvParameters){
  for(;;){
    ir_control.ExpectIRSignal();
    enemy_detector.Detect();
    line_detector.Detect();
  }
}

void RunStrategy(){
  switch (strat){
    case Strategy::kRadarEsq:
      Radar(Direction::kLeft);
      break;

    case Strategy::kRadarDir:
      Radar(Direction::kRight);
      break;

    case Strategy::kCurvaAbertaEsq:
      CurvaAberta(Direction::kLeft);
      break;

    case Strategy::kCurvaAbertaDir:
      CurvaAberta(Direction::kRight);
      break;

    case Strategy::kFollowOnly:
      Follow();
      break;

    case Strategy::kWoodPecker:
      Woodpecker();
      break;

    default:
      break;
  }
}

void Radar(Direction d){
  if (state == RobotState::kRunning){
    enemy_bits = enemy_detector.GetSensorEvents(30);
    if (!(enemy_bits & EVENT_SENSOR1) && !(enemy_bits & EVENT_SENSOR2) && !(enemy_bits & EVENT_SENSOR3) && !(enemy_bits & EVENT_SENSOR4)){
      motor_control.Turn(d);
    }
    else{
      while(state == RobotState::kRunning)
        Follow();
    }
  }
}

void CurvaAberta(Direction d){
  Direction opposite_d = d == Direction::kRight ? Direction::kLeft : Direction::kRight;
  if (state == RobotState::kRunning){
    time_1 = millis();
    while(millis() - time_1 <= 400)
      motor_control.Turn(d);
    motor_control.Accelerate(Direction::kForward);
    time_1 = millis();
    while(millis() - time_1 <= 1000){
      motor_control.Accelerate(Direction::kForward);
      enemy_bits = enemy_detector.GetSensorEvents(15);
      line_bit = line_detector.GetSensorEvents(15);
      if (line_bit & EVENT_QRE)
        LineDetectedProtocol(opposite_d);
    }
    motor_control.Turn(opposite_d); // precisa de um valor menor + precisa ir pra frente depois. Isso está errado. Grosso, Rude.
    vTaskDelay(400);
    Follow();
  }
}

/**
 * @todo Implementar a estratégia Woodpecker, que consiste em pequenas acelerações ("bicadinhas")
 * antes de uma eventual aceleração.
 */ 
void Woodpecker(){
  if (state == RobotState::kRunning){
    for (;;){}
  }
}

/// @brief Estratégia que segue o inimigo tentando centralizá-lo para acelerar em sua direção.
void Follow(){
  if (state == RobotState::kRunning){
    enemy_bits = enemy_detector.GetSensorEvents(15);
    line_bit = line_detector.GetSensorEvents(15);
    if (line_bit & EVENT_QRE){
      if (enemy_bits & EVENT_SENSOR1)
        LineDetectedProtocol(Direction::kLeft);
      else if (enemy_bits & EVENT_SENSOR4)
        LineDetectedProtocol(Direction::kRight);
      else
        LineDetectedProtocol(Direction::kLeft);
    }
    if (enemy_bits & EVENT_SENSOR1 ||
       ((enemy_bits & EVENT_SENSOR1) && (enemy_bits & EVENT_SENSOR2)) ||
       ((enemy_bits & EVENT_SENSOR1) && (enemy_bits & EVENT_SENSOR2) && (enemy_bits & EVENT_SENSOR3))){
      motor_control.Turn(Direction::kLeft);
    }
    else if (enemy_bits & EVENT_SENSOR4 ||
            ((enemy_bits & EVENT_SENSOR4) && (enemy_bits & EVENT_SENSOR3)) || 
            ((enemy_bits & EVENT_SENSOR4) && (enemy_bits & EVENT_SENSOR2) && (enemy_bits & EVENT_SENSOR3))){
      motor_control.Turn(Direction::kRight);
    }
    else if ((enemy_bits & EVENT_SENSOR2) || (enemy_bits & EVENT_SENSOR3) || ((enemy_bits & EVENT_SENSOR2) && (enemy_bits & EVENT_SENSOR3))){
      motor_control.Accelerate(Direction::kForward);
    }
  }
}

void LineDetectedProtocol(Direction direction){
  motor_control.Accelerate(Direction::kBackward);
  vTaskDelay(pdMS_TO_TICKS(300));
  motor_control.Turn(direction);
  vTaskDelay(pdMS_TO_TICKS(300));
}

The full code (all libraries and the rest of it) is available at this repo.

platformio.ini

I will also provide the platformio.ini file.

[env:esp32doit-devkit-v1]
platform = espressif32
board = esp32doit-devkit-v1
framework = arduino
lib_deps = 
	z3t0/IRremote@^4.5.0
	https://github.com/pololu/qtr-sensors-arduino.git#3.1.0
monitor_speed = 115200

Closing thoughts

I have tried different solutions listed in different topics to no avail. I am sorry if this is repetitious, ignorant or both, but thanks in advance for taking the time to read and and help, if possible.

I’m trying to reproduce but unfortunately, one library seems to be missing:

src/main.cpp:20:10: fatal error: Itamotorino.h: No such file or directory

*********************************************************************
* Looking for Itamotorino.h dependency? Check our library registry!
*
* CLI  > platformio lib search "header:Itamotorino.h"
* Web  > https://registry.platformio.org/search?q=header:Itamotorino.h
*
*********************************************************************

   20 | #include <Itamotorino.h>
      |          ^~~~~~~~~~~~~~~
compilation terminated.
*** [.pio/build/esp32doit-devkit-v1/src/main.cpp.o] Error 1

Oh! I’m sorry, I forgot to update my .gitignore file before publishing this topic. This library is an adapter for DC motor control with the TB6612FNG motor driver (At least this is the one I use) which is not available at the registry. The library is now available at the solid_class_implementation branch of my repository, and its original code is available here.

No it’s still complaining about missing Itamotorino.h
You excluded this library in .gitignore:

.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch
lib/Itamotorino/
.gitignore
include/new_implementations/

Weird. I made a commit earlier removing lib/Itamotorino from the .gitignore file (as per 5d61316) at the solid_class_implementation branch. Is it inacessible somehow?

Ok I got it working and also found the cause of the issue: It’s the IRemote library.
This one is a bit tricky because it’s a header only library.

To fix the multiple definition errors move the line “#include <IRemote.hpp>” from “ir_control.hpp” to “ir_control.cpp”

Additional errors:

To fix the “-Wwrite-strings” errors in

  • ir_control.cpp
  • main.cpp
  • line_detector.cpp
  • nvs_control.cpp
  • enemy_detector.cpp
  • motor_control.cpp
    change
static constexpr char* ...

to

static constexpr const char* ...

In ir_control.cpp line 23 you used the wrong quotation marks ' instead of "
Change line 23 to

Serial.print("Received command: ");
1 Like

Crazy work. I can’t believe it worked!

1 Like

Thank you so much for the help. I’m sorry if I came off wrong earlier. Happy new Year!

1 Like