Linker Error undefined reference on own code

Hello,
I’m new to platformio and only have slight experience with c++, so maybe this problem is easily fixed, but by now I’m slowly losing my mind over it.
I started a project to build a KVM-Switch out of several cheap KVM-Switches and control it with an arduino. I wanted to check my project and tried to run the build, to see if there are any errors. The source files build, but they don’t link.

A link to a zip file of my project code is here.
The error message is as follows:

In function `main':
<artificial>:(.text.startup+0x1c): undefined reference to `pin::Input::Input(unsigned char, bool, unsigned int)'
<artificial>:(.text.startup+0x2c): undefined reference to `pin::Input::Input(unsigned char, bool, unsigned int)'
<artificial>:(.text.startup+0x42): undefined reference to `pin::OutputTrigger::OutputTrigger(unsigned char, pin::state, unsigned long)'
<artificial>:(.text.startup+0x5c): undefined reference to `kvm::Switch::Switch(pin::Input*, pin::Input*, pin::OutputTrigger*)'

The functions (constructors) exist, seem to be accessible and I checked the types over and over again and to me it seems the signatures are the same. I hope someone is able to help me here :slight_smile: Thank’s in advance!

All of your classes are implemented in the wrong way.

In the header you e.g. write

#pragma once
#include <Arduino.h>
#include "arduino/pins/state.h"
#include "util/observe/observable.h"

namespace pin{
    class Input{
        public:
            Input(uint8_t pin, bool useInternalPullup, unsigned int debounce = 0);

            state getState();

            observe::Observable* asObservable();

            void loop();
    };
}

Then, you write in the .cpp file

#include <Arduino.h>
#include "arduino/pins/state.h"
#include "util/observe/observable.h"

namespace pin{
    class Input{
        public:
            Input(unsigned pin, bool useInternalPullup, unsigned int debounce):
            pin(pin), debounce(debounce){
                pinMode(this->pin, useInternalPullup ? INPUT_PULLUP : INPUT);
                this->observable = observe::Observable();
                this->informed = false;
            }

            observe::Observable* asObservable(){
                return &(this->observable);
            }

            state getState(){
                if(this->stateIsStable()){
                    return this->lastState;
                }
                return state::floating;
            }

            void loop(){
                auto time = millis();
                state state = digitalRead(this->pin) == HIGH ? state::high : state::low;
                if(state != this->lastState){
                    this->lastState = state;
                    this->lastStateChange = time;
                    this->informed = false;
                }

                if(!this->informed && this->stateIsStable()){
                    this->observable.informObservers();
                }
            }

        private:
            uint8_t pin;
            state lastState;
            bool informed;
            unsigned long lastStateChange;
            unsigned int debounce;
            observe::Observable observable;

            bool stateIsStable(){
                auto time = millis();
                if(time < this->lastStateChange){
                    auto max = 0UL - 1UL;

                    return ((max - this->lastStateChange) + time) >= this->debounce;
                }

                return (time - this->lastStateChange) >= this->debounce;
            }
    };
}

This is wrong because of multiple reasons:

  1. you’re redeclaring (with the class keyword) what was already in the header and implementing the class. You just need to be implementing it.
  2. Your .cpp and .h declare some functions with different types. E.g., the constructor in the .h file starts with Input(uint8_t pin, the .cpp one is Input(unsigned pin.
  3. Your .h file does not declare the private methods + fields used in the .cpp file. The .cpp simple declares its own class on the fly, completely detached from the input.h file (it does also not #include it)

See include https://www.cplusplus.com/doc/tutorial/classes/ and coding style - Correct way to define C++ namespace methods in .cpp file - Stack Overflow.

The correct form in tje input.h file would be, in your case would be

#pragma once
#include <Arduino.h>
#include "arduino/pins/state.h"
#include "util/observe/observable.h"

namespace pin{
    class Input{
        public:
            Input(uint8_t pin, bool useInternalPullup, unsigned int debounce = 0);

            state getState();

            observe::Observable* asObservable();

            void loop();

        private:
            uint8_t pin;
            state lastState;
            bool informed;
            unsigned long lastStateChange;
            unsigned int debounce;
            observe::Observable observable;
            bool stateIsStable();
    }; 
}

and in the input.cpp file

#include <Arduino.h>
#include "arduino/pins/state.h"
#include "util/observe/observable.h"
#include "arduino/pins/controllers/input.h"

namespace pin
{
    Input::Input(uint8_t pin, bool useInternalPullup, unsigned int debounce) 
        : pin(pin), debounce(debounce)
    {
        pinMode(this->pin, useInternalPullup ? INPUT_PULLUP : INPUT);
        this->observable = observe::Observable();
        this->informed = false;
    }

    observe::Observable *Input::asObservable()
    {
        return &(this->observable);
    }

    state Input::getState()
    {
        if (this->stateIsStable())
        {
            return this->lastState;
        }
        return state::floating;
    }

    void Input::loop()
    {
        auto time = millis();
        state state = digitalRead(this->pin) == HIGH ? state::high : state::low;
        if (state != this->lastState)
        {
            this->lastState = state;
            this->lastStateChange = time;
            this->informed = false;
        }

        if (!this->informed && this->stateIsStable())
        {
            this->observable.informObservers();
        }
    }
    bool Input::stateIsStable()
    {
        auto time = millis();
        if (time < this->lastStateChange)
        {
            auto max = 0UL - 1UL;

            return ((max - this->lastStateChange) + time) >= this->debounce;
        }
        return (time - this->lastStateChange) >= this->debounce;
    }
};

Now the recompiled firmware throws the error

Linking .pio\build\megaatmega2560\firmware.elf
C:\Users\Max\AppData\Local\Temp\cc8z5Ry3.ltrans0.ltrans.o: In function `main':
<artificial>:(.text.startup+0x4e): undefined reference to `pin::OutputTrigger::OutputTrigger(unsigned char, pin::state, unsigned long)'
<artificial>:(.text.startup+0x68): undefined reference to `kvm::Switch::Switch(pin::Input*, pin::Input*, pin::OutputTrigger*)'
collect2.exe: error: ld returned 1 exit status
*** [.pio\build\megaatmega2560\firmware.elf] Error 1

notice how the previous errors regarding the Input class

<artificial>:(.text.startup+0x1c): undefined reference to `pin::Input::Input(unsigned char, bool, unsigned int)'
<artificial>:(.text.startup+0x2c): undefined reference to `pin::Input::Input(unsigned char, bool, unsigned int)'

are gone.

Repeat this for every one of your mis-written cpp / h files and the firmware shall compile.

1 Like

Hi maxgerhardt,

thank you very much for your answer. I’ll try your suggestions :slight_smile:.
The stuff you write looks familiar (my cpp course is quite some time in the past) and now I think I got things mixed up with other languages I use much more regularly.

Again thank you for taking the time to dig through my code and helping me out :slight_smile: