Ошибка компиляции скетча для преобразования класса/библиотеки.

Я пытаюсь взять скетч и превратить его в класс для создаваемой мной библиотеки, но у меня возникают проблемы с компиляцией.

Это не дубликат, как предлагалось. как я объясняю в комментарии и своем ответе. Спасибо всем за вашу помощь - поскольку я новичок в создании библиотек - решил, что попробую что-нибудь правильно для разнообразия :)

Вот исходный скетч: Добавлен здесь

#include <ros.h>
#include <sensor_msgs/BatteryState.h>

#define K           0.00472199
#define CELLS       6
#define MAX_CELLS   12

double cell_const[MAX_CELLS] = 
{
  1.0000,
  2.1915,
  2.6970,
  4.1111,
  4.7333,
  6.6000,
  6.6000,
  7.8293,
  8.4667,
  9.2353,
  11.0000,
  11.0000
};

ros::NodeHandle nh;
sensor_msgs::BatteryState batt_state;
ros::Publisher batteryState("battery_state", &batt_state);

void setup() 
{
  // Инициализируем узел ROS.
  nh.initNode();
  nh.advertise(batteryState);

  // Заполняем параметры батареи.
  batt_state.design_capacity = 2200;      // мАч
  batt_state.power_supply_status = 2;     // разрядка
  batt_state.power_supply_health = 0;     // неизвестный
  batt_state.power_supply_technology = 3; // ЛиПо
  batt_state.present = 1;                 // батарея присутствует

  batt_state.location = "Crawler";        // местоположение юнита
  batt_state.serial_number = "ABC_0001";  // серийный номер устройства
  batt_state.cell_voltage = new float[CELLS];

}

void loop() 
{
  // Заряд батареи.
  double battVoltage = 0.0;
  double prevVoltage = 0.0;

  // Заполняем сообщение о состоянии батареи.
  for (int i = 0; i < CELLS; i++)
  {
     // Чтение необработанного напряжения с аналогового контакта.
    double cellVoltage = analogRead(i) * K;

    // Масштабируем показания до полного напряжения.
    cellVoltage *= cell_const[i];
    double tmp = cellVoltage;

    // Изолируем текущее напряжение ячейки.
    cellVoltage -= prevVoltage;
    battVoltage += cellVoltage;
    prevVoltage = tmp;

    // Установить текущее напряжение ячейки в сообщение.
    batt_state.cell_voltage[i] = (float)cellVoltage;

    // Проверяем, подключена ли батарея.
    if (batt_state.cell_voltage[i] >= 2.0)
      batt_state.present = 1;
    else
      batt_state.present = 0;
  }

  // Обновляем состояние батареи.
  batt_state.voltage = (float)battVoltage;

  if (batt_state.voltage > CELLS * 4.2)
    batt_state.power_supply_health = 4; // перенапряжение
  else if (batt_state.voltage < CELLS * 3.0)
    batt_state.power_supply_health = 3; // мертвый
  else
    batt_state.power_supply_health = 1; // хороший

  // Публикация данных в ROSSERIAL.
  batteryState.publish( &batt_state );
  nh.spinOnce();
  delay(1000);

}

Вот мой заголовочный файл:

#ifndef battery_h
#define battery_h


#if ARDUINO >= 100
#include "Arduino.h"
#else
#include "WProgram.h"
#endif

#include <ros.h>
#include <sensor_msgs/BatteryState.h>

#define K           0.00472199
#define CELLS       6
#define MAX_CELLS   12
#define CRITICAL    0.30

double cell_const[MAX_CELLS] = 
{
  1.0000, 2.1915, 2.6970, 4.1111,
  4.7333, 6.6000, 6.6000, 7.8293,
  8.4667, 9.2353, 11.0000, 11.0000
};

ros::NodeHandle nh;
sensor_msgs::BatteryState batt_state;

ros::Publisher batteryState("/battery/info", &batt_state);

class Battery
{
  private:
   void updateBatteryHealth();
   double battVoltage;
   double prevVoltage; 
  public:
    void begin();
    void updateBatteryState();
};

#endif

Это мой файл cpp:

#include "battery.h"


void Battery::begin(){

  // Запускаем узел ROS и устанавливаем параметры.
  nh.initNode();

  // Настройка издателей/подписчиков.
  nh.advertise(batteryState);

  // Заполняем параметры батареи.
  batt_state.design_capacity          = 2200;  // мАч
  batt_state.power_supply_status      = 2;     // разрядка
  batt_state.power_supply_health      = 0;     // неизвестный
  batt_state.power_supply_technology  = 3;     // ЛиПо
  batt_state.present                  = 1;     // батарея присутствует

  batt_state.location      = "Crawler";        // местоположение юнита
  batt_state.serial_number = "ABC_0001";       // серийный номер устройства

  batt_state.cell_voltage = new float[CELLS];  // здоровье отдельной клетки


}  

void Battery::updateBatteryState() {
  battVoltage = 0.0;
  prevVoltage = 0.0;
  // Сброс состояния источника питания.
  batt_state.power_supply_health = 0;
    // Заполняем сообщение о состоянии батареи.
  for (int i = 0; i < CELLS; i++)
  {
    // Чтение необработанного напряжения с аналогового контакта.
    double cellVoltage = analogRead(i) * K;

    // Масштабируем показания до полного напряжения.
    cellVoltage *= cell_const[i];
    double tmp = cellVoltage;

    // Изолируем текущее напряжение ячейки.
    cellVoltage -= prevVoltage;
    battVoltage += cellVoltage;
    prevVoltage = tmp;

    // Установить текущее напряжение ячейки в сообщение.
    batt_state.cell_voltage[i] = (float)cellVoltage;

    // Проверяем, подключена ли батарея.
    if (batt_state.cell_voltage[i] >= 2.0)
    {
      if (batt_state.cell_voltage[i] <= 3.2)
        batt_state.power_supply_health = 5; // Неуказанный сбой.
      batt_state.present = 1;
    }
    else{
      batt_state.present = 0;
    }
  }
};

void Battery::updateBatteryHealth() {
    // Обновляем состояние батареи.
  if (batt_state.present)
  {
    batt_state.voltage = (float)battVoltage;
    float volt = batt_state.voltage;
    float low  = 3.0 * CELLS;
    float high = 4.2 * CELLS;
    batt_state.percentage = constrain((volt - low) / (high - low), 0.0, 1.0);    
  }
  else 
  {
    batt_state.voltage = 0.0;
    batt_state.percentage = 0.0;
  }

  // Обновить состояние источника питания, если не удалось.
  if (batt_state.power_supply_health == 0 && batt_state.present)
  {
    if (batt_state.voltage > CELLS * 4.2)
      batt_state.power_supply_health = 4; // перенапряжение
    else if (batt_state.voltage < CELLS * 3.0)
      batt_state.power_supply_health = 3; // мертвый
    else
      batt_state.power_supply_health = 1; // хороший
  }
   // Публикация данных в ROSSERIAL.
  batteryState.publish( &batt_state );
  nh.spinOnce();
};

это ошибка, которую я получаю, просто вызывая функцию Battery::begin в моем основном классе:

libraries/KBROSRobot/KBROSRobot.cpp.o (symbol from plugin): In function `KBROSRobot::KBROSRobot()':
(.text+0x0): multiple definition of `nh'
sketch/rob_test.ino.cpp.o (symbol from plugin):(.text+0x0): first defined here
libraries/KBROSRobot/KBROSRobot.cpp.o (symbol from plugin): In function `KBROSRobot::KBROSRobot()':
(.text+0x0): multiple definition of `batt_state'
sketch/rob_test.ino.cpp.o (symbol from plugin):(.text+0x0): first defined here
libraries/KBROSRobot/KBROSRobot.cpp.o (symbol from plugin): In function `KBROSRobot::KBROSRobot()':
(.text+0x0): multiple definition of `batteryState'
sketch/rob_test.ino.cpp.o (symbol from plugin):(.text+0x0): first defined here
libraries/KBROSRobot/KBROSRobot.cpp.o (symbol from plugin): In function `KBROSRobot::KBROSRobot()':
(.text+0x0): multiple definition of `cell_const'
sketch/rob_test.ino.cpp.o (symbol from plugin):(.text+0x0): first defined here
/var/folders/m4/lq13_j5j23qc0l2wp8dqmt4h0000gp/T//cczNyzqC.ltrans1.ltrans.o: In function `begin':
/Users/keith.brown/Documents/Arduino/libraries/KBROSRobot/KBROSRobot.cpp:9: undefined reference to `Battery::begin()'
collect2: error: ld returned 1 exit status
exit status 1

, 👍-1

Обсуждение

Возможный дубликат Ошибка множественного определения при связывании прошивки.elf, @KIIV

@KIIV, как это дубликат? это был беспорядок с защитой заголовков и расширениями файлов. здесь компоновщик не может найти функцию, которая существует в исходном коде. пожалуйста, объясни, если ты видишь что-то, чего я не вижу, @Juraj

@Juraj В ответе я попытался рассказать, как объявить что-то в заголовке и определить это в исходном файле, чтобы это не приводило к множественным определениям., @KIIV

@KIIV, извини, да, но ответ tttapa касается и этого. но последняя ошибка, возможно, является чем-то другим неопределенной ссылкой на Battery::begin(), @Juraj

@Juraj Правда, правда. Неопределенная батарея::begin — это странно. Я не вижу в вопросе ничего, что объясняло бы, почему он терпит неудачу., @KIIV

@Juraj и KIIV проверяют мой ответ ниже. У меня каждый класс находился в именованных папках /sensors/battery... Drive/motors... /utility/statusLights... каждый другой класс нужно было поместить в папку утилит... поэтому класс Battery не компилировался., @rcpilotp51


2 ответа


Лучший ответ:

1

Не определяйте глобальные переменные в заголовочном файле. Только объявляйте их, используя внешнюю ссылку. Затем определите их в файле реализации

Заголовок (.h):

extern ros::NodeHandle nh;

Реализация (.cpp):

ros::NodeHandle nh;

Старайтесь свести использование глобальных переменных к минимуму. Если они вам не нужны вне библиотеки, объявите их в файле реализации и пометьте как статические.
Или еще лучше: инкапсулируйте их внутри класса.


Причина появления этих ошибок заключается в том, что каждый файл реализации компилируется отдельно. У вас есть два файла реализации, которые включают файл заголовка, поэтому файл заголовка компилируется дважды. В результате появятся две глобальные переменные с именем nh. На этапе связывания компоновщик пытается объединить скомпилированные версии KBROSRobot.cpp и rob_test.ino.cpp (они называются объектными файлами, .o ). Компоновщик не может узнать, какая переменная какая, поскольку обе они определены дважды, поэтому он выдает ошибку.

Несколько объявлений — это нормально, поскольку они просто сообщают компилятору, что «где-то есть определение», и компоновщик рано или поздно обнаружит это определение. Вот почему объявление с внешней связью решает вашу проблему.
Множественные определения не допускаются (в большинстве случаев).

,

я попробую вышеописанное - как вы думаете, я правильно конвертировал эскиз? должен ли я сделать что-то еще по-другому?, @rcpilotp51

@ rcpilotp51 Вы должны сделать то же самое для всех определений в заголовочном файле. Я не рассматривал остальную часть вашего кода подробно., @tttapa

я получаю сообщение об ошибке по поводу extern: предупреждение: 'batteryState' инициализирован и объявлен 'extern' extern ros::Publisher BatteryState("/battery/info", &batt_state);, @rcpilotp51

@rcpilotp51 удалите инициализатор в заголовочном файле (т.е. просто используйте extern ros::Publisher BatteryState;). Добавьте инициализатор в файл реализации., @tttapa

ок, избавился от множества ошибок, теперь просто осталось: var/folders/m4/lq13_j5j23qc0l2wp8dqmt4h0000gp/T//ccB5PTqK.ltrans0.ltrans.o: В функции begin': /Users/keith.brown/Documents/Arduino/libraries/KBROSRobot/KBROSRobot.cpp:9: неопределенная ссылка на Battery::begin()' Collect2: ошибка: ld вернул 1 статус выхода статус выхода 1, @rcpilotp51

если указанная выше переменная является «внешней», следует ли мне определить ее в разделе «private» var? @tttapa, @rcpilotp51

@ rcpilotp51 Вы не определили void Battery::begin() в своем файле реализации. Нет, переменные класса и члена не должны иметь пометку extern., @tttapa

он определен в классе Battery .h и реализован как Battery::begin() в cpp-файле void Begin(); в заголовке. @tttapa, @rcpilotp51

@ rcpilotp51 Компилятор, похоже, не согласен. Проверьте точки с запятой и скобки. Также имейте в виду, что он использует версию из папки «libraries»., @tttapa

Я редактирую прямо в папке библиотеки. насколько я могу судить, оно там.... есть еще предложения? @tttapa, @rcpilotp51


0

Я также обнаружил, что все остальные классы в библиотеке должны находиться в папке utility, иначе они не будут компилироваться. Отвечаю на мой комментарий выше. Спасибо @tttapa за вашу помощь.

Теперь мне осталось грамотно запрограммировать робота, что, к сожалению, субъективно... ;)

,

Спецификации структуры папок библиотеки: https://github.com/arduino/Arduino/wiki/Arduino-IDE-1.5:-Library-спецификация, @Juraj