Getting a Singleton to work on Arduino

My Project is to build a laser shooting game for my son's birthday.

I am using this setup:

  • Arduino Mega

  • One Button to start the game

  • One red led, one green led and a buzzer

  • 10 Servos and 10 LDR as targets attached to the servos

  • One Dashboard to calibrate the servos consisting of

  • a confirmation button

  • a 10k ohm potentiometer to adjust the angle for up position and down position of the servo

  • a red led and a green led

The whole project consists of these files (objects)

  • Lasershiessen.ino - First of all a sketch to put the objects in place with setup and loop where Loop checks whether the start game button was pushed

  • pinconf.h holds the definition of what is attached to which pin on the board
    pitches.h does the same for sounds. Both are #define all over

  • Taget.h / Target.cpp implementing the behaviour of the targets (servo + LDR)

  • CalibrationDashboard.h / ~.cpp implementing the dashboard for calibration of the servos. As there is only one such dashboard for all targets this should be a singleton object

  • Lasergame.h / ~.cpp designed to contain different rules for the game at a later stage. For now when the game starts all targets should be up and whenever the LDR of a target is hit it goes down until all targets are hit. Then the game is over and it returns the time the player took to hit all targets.

It's been quite a while since I last programmed something in C++ and I am relatively new to Arduino. Professionally I don't go beyond a VBA macro every once in a while. So my code probably is everything but professional. :confused:
I find myself now at a stage where apparently the linker has some problem and I have no clue. I'd be happy if anyone could give me a helpful hint :slight_smile:

After a few warnings on sprintf-usage this is the error message where the compilation exits, which apparently has something to do with my singleton:

C:\Users\RD1979\AppData\Local\Temp\ccTDFi7p.ltrans0.ltrans.o: In function `main':

ccTDFi7p.ltrans0.o:(.text.startup+0x17e): undefined reference to `CalibrationDashboard::pInstance'

ccTDFi7p.ltrans0.o:(.text.startup+0x182): undefined reference to `CalibrationDashboard::pInstance'

ccTDFi7p.ltrans0.o:(.text.startup+0x1a4): undefined reference to `CalibrationDashboard::pInstance'

ccTDFi7p.ltrans0.o:(.text.startup+0x1a8): undefined reference to `CalibrationDashboard::pInstance'

ccTDFi7p.ltrans0.o:(.text.startup+0x1ac): undefined reference to `CalibrationDashboard::pInstance'

C:\Users\RD1979\AppData\Local\Temp\ccTDFi7p.ltrans0.ltrans.o:ccTDFi7p.ltrans0.o:(.text.startup+0x1b0): more undefined references to `CalibrationDashboard::pInstance' follow

collect2.exe: error: ld returned 1 exit status

Laserschiessen.ino (4.22 KB)

CalibrationDashboard.h (766 Bytes)

CalibrationDashboard.cpp (1.94 KB)

Target.h (757 Bytes)

Lasergame.h (412 Bytes)

Please post your code inline with tags.
Anyone on a tablet can’t open your files, and it sounds like you’re going to need a bit of help.

In your BOM, you omitted the lasers?

You've declared the pInstance variable in the class, but you haven't defined it. You need to add this to the cpp file:

CalibrationDashboard* CalibrationDashboard::pInstance;

By the way, you only put include guards in their respective header files. No need to duplicate them in the files doing the include, eg. ino.

The laser is not connected to the Arduino. It sits in a modified toy gun which came with sound and thus had a battery (4.5 V).

Here is the code (the post with all the code was to long, so I had to shorten it a bit):
CalibrationDashboard.h:

class CalibrationDashboard 
{
public:

static CalibrationDashboard& Instance(); // Class method as the only access to the Singleton Object

 int getAnalog();
 bool getButton();
 bool getRedLed();
 void setRedLed(bool onOff);
 bool getGrnLed();
 void setGrnLed(bool onOff);

protected:

 unsigned long btnLastMillis;
 bool btnLastResult;

private:

static CalibrationDashboard* pInstance; // Static variable holding the pointer to the only instance of this
CalibrationDashboard();
};

CalibrationDashboard.cpp:

#ifndef pinconf
#include "pinconf_MVP1.h"
#endif
#include "CalibrationDashboard.h"

CalibrationDashboard& CalibrationDashboard::Instance(){
	//static Cleanup cleanup;
	if (CalibrationDashboard::pInstance == nullptr)
		CalibrationDashboard::pInstance = new CalibrationDashboard();
	return *CalibrationDashboard::pInstance;
}

// There is only this private constructor, no public one!
CalibrationDashboard::CalibrationDashboard(){
	pinMode(buttonPin, INPUT); // 10k ohm pulldown resistor attached
	pinMode(ledRedPin, OUTPUT);
	pinMode(ledGrnPin, OUTPUT);
}

/**
	Returns current value from the defined analog Pin
*/
 int CalibrationDashboard::getAnalog(){
	return analogRead(analogPin);	
}

/**
	while-friendly: returns true until button is pushed
*/
 bool CalibrationDashboard::getButton(){
	if (CalibrationDashboard::btnLastMillis - millis() > 20)
	{
		CalibrationDashboard::btnLastMillis = millis();
		if(digitalRead(buttonPin)==LOW)
		{
			CalibrationDashboard::btnLastResult = true;
			return true;
		}
		else
		{
			CalibrationDashboard::btnLastResult = false;
			return false;
		}
	}
	return CalibrationDashboard::btnLastResult;
}

/**
Get and set status of attached LEDs
*/
 bool CalibrationDashboard::getRedLed(){
	return digitalRead(ledRedPin);
}
 void CalibrationDashboard::setRedLed(bool onOff){
	digitalWrite(ledRedPin, onOff);
}
 bool CalibrationDashboard::getGrnLed(){
	return digitalRead(ledGrnPin);
}
 void CalibrationDashboard::setGrnLed(bool onOff){
	digitalWrite(ledGrnPin, onOff);
}

Target.h:

#include "Arduino.h"
#include <Servo.h>
#include "CalibrationDashboard.h"
#include <assert.h>

class Target
{
public:
	Target(int servoPin, int ldrPin);
	~Target();
	void adjustServo(CalibrationDashboard* calDb);
	bool evalStatus();
	void reInit();
	byte getUpPos();
	byte getDownPos();

private:
	bool _isInited;
	Servo _srv;
	int _ldrPin;
	bool _alive;
	long _msTargetDied;
	int _ldrThreshold;
	byte _upPos;
	byte _downPos;

	//int _calibrateLdr();
	int _calibrateLdr(int calibReadings, long readIntervall);
	bool _chkLdrHit();
	void _updPosition();

};

Target.cpp:

#include "Target.h"

/**
Constructor 
*/
Target::Target(int servoPin, int ldrPin){
	...}


void Target::reInit(){
	…
}

Target::~Target(){
	…
}

void Target::adjustServo(CalibrationDashboard* calDb){
	const byte BLINK_INT = 100;
	const byte DEBOUNCE = 15;
	long ms;
	int angle;
	
	ms = 0;
	angle = calDb->getAnalog();
	Target::_srv.write(constrain(angle, 0, 180));

	// adjust servo using analog reference
	while (calDb->getButton()) {
		// adjust down position until button is pressed
		angle = map(calDb->getAnalog(), 0, 1023, 0, 180);
		if (Target::_srv.read() != angle) Target::_srv.write(angle);
		// Red LED blinking during this
		if (millis() - ms > BLINK_INT) {
			ms = millis();
			calDb->setRedLed(!calDb->getRedLed());
		}
		delay(DEBOUNCE);
	}
	Target::_upPos = angle;
	// confirm with red LED on
	calDb->setRedLed(true);
	delay(2000);
	calDb->setRedLed(false);
	delay(100);

	while (calDb->getButton()) {
		// adjust up position until button is pressed
		angle = map(calDb->getAnalog(), 0, 1023, 0, 180);
		if (Target::_srv.read() != angle) Target::_srv.write(angle);
		// Green LED blinking during this
		if (millis() - ms > BLINK_INT) {
			ms = millis();
			calDb->setGrnLed(!calDb->getGrnLed());
		}
		delay(DEBOUNCE);
	}
	Target::_downPos = angle;
	// confirm with green LED on
	calDb->setGrnLed(true);
	delay(2000);
	calDb->setGrnLed(false);
	delay(100);
	Target::_isInited = true;
}

/**
Returns status of the target: true = active; false = dead
Check the Hit-Status
*/
bool Target::evalStatus(){
	...
}

/**
Returns calculated correction factor * average value of readings + 1/2 * range
int calibReadings - number of readings for average
long readIntervall - pause between readings in ms
*/
int Target::_calibrateLdr(int calibReadings, long readIntervall){
	...
}

/**
Returns whether LDR-resistance is below defined Threshold
If Threshold is passed, Target is hit and then !_alive thereafter
*/
bool Target::_chkLdrHit(){
	...}

/**
Adjusts the servo according to the target's _alive status
*/
void Target::_updPosition(){
	…
}

byte Target::getUpPos(){
	…
}

byte Target::getDownPos(){
	…
}

Laserschiessen.ino:

// Testausgaben zentral steuern:
#define logging
//Bibliotheken
#define __ASSERT_USE_STDERR

#include <assert.h>
// #include <EEPROM.h>
// Ton-Definitionen
#ifndef pitches_h
#include "pitches.h"
#endif
// Pin-Belegung auf Arduino Mega mit 10 LDR+Servos
#ifndef pinconf
#include "pinconf_MVP1.h"
#endif
#ifndef CalibrationDashboard_h
#include "CalibrationDashboard.h"
#endif
#ifndef Target_h
#include "Target.h"
#endif
#ifndef Lasergame_h
#include "Lasergame.h"
#endif

// Prepare Targets
//int targetInfo[cntSens][2] {{sp0, ldr0}, {sp1, ldr1}, {sp2, ldr2}, {sp3, ldr3}, {sp4, ldr4}, {sp5, ldr5}, {sp6, ldr6}, {sp7, ldr7}, {sp8, ldr8}, {sp9, ldr9}};
  Target t0(sp0, ldr0);
  Target t1(sp1, ldr1);
  Target t2(sp2, ldr2);
  Target t3(sp3, ldr3);
  Target t4(sp4, ldr4);
  Target t5(sp5, ldr5);
  Target t6(sp6, ldr6);
  Target t7(sp7, ldr7);
  Target t8(sp8, ldr8);
  Target t9(sp9, ldr9);
  Target* targets[cntSens] {&t0, &t1, &t2, &t3, &t4, &t5, &t6, &t7, &t8, &t9};

//  CalibrationDashboard cdb; 
  CalibrationDashboard* calDbrd; // Singleton!
// - SETUP -------------------------------------------------------------------------------------------
void setup() {
  
  // Serieller Monitor
  Serial.begin(9600); // initialize serial communications at 9600 bps
#ifdef logging
  Serial.println("- - - - - setup startet - - - - -");
#endif  
  // digitale Eingänge initialisieren
  pinMode(btnY, INPUT_PULLUP);
#ifdef logging
  Serial.println("Yellow Button bereit (startet Kalbrieren neu)");
#endif
  pinMode(btnR, INPUT_PULLUP);
#ifdef logging  
  Serial.println("Red Button bereit (bricht laufendes Spiel ab)");
#endif
// Instanzen für Ziele erstellen
calDbrd = &calDbrd->Instance();
/*
  for (int i = 0; i < cntSens; i++) {
    targets[i] = new Target(targetInfo[i][0], targetInfo[i][1]);
	#ifdef logging
      Serial.print("Target Nr. "); Serial.print(i + 1); Serial.println(" erstellt");
    #endif
	delay(100);
	}
 */
#ifdef logging
  Serial.println("Ziele bereit");
#endif
  // digitale Ausgänge
  pinMode(redLed, OUTPUT);
  digitalWrite(redLed, HIGH);
#ifdef logging
  Serial.println("Rote Status-LED bereit (Spiel läuft)");
#endif
  pinMode(grnLed, OUTPUT);
  digitalWrite(grnLed, HIGH);
#ifdef logging
  Serial.println("Grüne Status-LED bereit (Spiel bereit)");
#endif
  // Info-Ton
  tone(buzzPin, NOTE_C7, 500);
  // Status LED ausgeschaltet initialisieren
  digitalWrite(redLed, LOW); digitalWrite(grnLed, LOW);
  // Servos kalibrieren
  //calDbrd& = calDbrd->Instance();
  for (int i = 0; i < cntSens; i++) {
    targets[i]->adjustServo(calDbrd);
	#ifdef logging
	  Serial.print("Target Nr. "); Serial.print(i + 1); Serial.println(" kalibriert");
	#endif
	tone(buzzPin, NOTE_C5, 250);
  }
#ifdef logging
  Serial.println("Ziele kalibriert");
#endif
  digitalWrite(redLed, LOW); digitalWrite(grnLed, HIGH);
#ifdef logging
  Serial.println("- - - - - setup fertig - - - - -");
#endif  

}

// - LOOP ---------------------------------------------------------------------------------------------
void loop() {

        // Yellow button starts the game
  if (digitalRead(btnY) == HIGH) {
    Serial.println("+ + + Spiel startet + + +");
    Lasergame game(targets);
	digitalWrite(redLed, HIGH); digitalWrite(grnLed, LOW);
	Serial.println(sprintf("Spieldauer %f ms", game.start()/1000));
	digitalWrite(redLed, LOW); digitalWrite(grnLed, HIGH);
	Serial.println("+ + + Spiel beendet + + +");
  }
}

arduarn:
You've declared the pInstance variable in the class, but you haven't defined it. You need to add this to the cpp file

It worked like a charm - THANK YOU!!!

arduarn:
By the way, you only put include guards in their respective header files. No need to duplicate them in the files doing the include, eg. ino.

Thank you for this hint too, dropped it in the code now.