EEVblog Electronics Community Forum

Products => Computers => Programming => Topic started by: ksoviero on August 12, 2020, 10:45:50 pm

Title: I don't understand libraries in C++
Post by: ksoviero on August 12, 2020, 10:45:50 pm

Complete beginner to C/C++ here...

So, I'm trying to include the Arduino SoftwareSerial library in my otherwise vanilla AVR project (mostly out of laziness). However, when I build I get an "undefined reference" error.

Here's the code in question:

Code: [Select]
#include <avr/io.h>
#include <SoftwareSerial.h>

#define DEBUG_TX PB0
#define DEBUG_RX PB1

SoftwareSerial debug = SoftwareSerial(DEBUG_RX, DEBUG_TX);

int main() {
    return 0;
}

And here's the command I'm using to build and the output:

Code: [Select]
avr-g++ -Ilib -I/usr/share/arduino/libraries/SoftwareSerial -I/usr/share/arduino/hardware/arduino/cores/arduino -g -Os -mmcu=atmega328p -Wall *.cpp -o example.bin -DF_CPU=8000000L
/tmp/ccsvCELp.o: In function `_GLOBAL__sub_I_debug':
/home/ksoviero/VS Code/Example/main.cpp:8: undefined reference to `SoftwareSerial::SoftwareSerial(unsigned char, unsigned char, bool)'
/tmp/ccsvCELp.o: In function `_GLOBAL__sub_D_debug':
/home/ksoviero/VS Code/Example/main.cpp:(.text.exit+0x4): undefined reference to `SoftwareSerial::~SoftwareSerial()'
collect2: error: ld returned 1 exit status
make: *** [Makefile:13: build] Error 1

So, without resorting to some premade tool like arduino-mk, how would I make this work manually? I want to learn how this works, not just make it work for the sake of working.

Thanks!
Title: Re: I don't understand libraries in C++
Post by: langwadt on August 12, 2020, 10:54:08 pm
https://www.arduino.cc/en/Reference/SoftwareSerial (https://www.arduino.cc/en/Reference/SoftwareSerial)
Title: Re: I don't understand libraries in C++
Post by: ataradov on August 12, 2020, 11:04:58 pm
You can't just 'g++' on a single file. You need to link a whole lot of libraries. So while you may not need a full Arduino Makefile, you still need something that will do this.

"#include" just includes a header file so that function prototypes and data structures are declared. This does not bring in the actual code.
Title: Re: I don't understand libraries in C++
Post by: AntiProtonBoy on August 13, 2020, 02:04:52 am
Those look like linker errors. The linker is unable to locate the binary symbols for the SoftwareSerial constructor and destructor functions. Did you tell the linker where to locate the SoftwareSerial binary library?

Title: Re: I don't understand libraries in C++
Post by: westfw on August 13, 2020, 02:19:32 am
Your compile command only compiles the C++ files in your /home/ksoviero/VS Code/Example/ directory.  You also need to compile the SoftwareSerial library (whatever source files it uses), and link the results in with your source objects.

Also, you have bit numbers (PB0, PB1) where you need Arduino Board Pin Numbers.

It might be easiest to just copy SoftwareSerial.cpp into your source directory.

(This is something that the Arduino IDE does with it's "handholding" - given an "#include <libraryname>" and some databased of installed libraries, the IDE will find the source, compile it, and link in the binaries.  Step into bare C, and you have to do that all yourself...)
Title: Re: I don't understand libraries in C++
Post by: fanOfeeDIY on August 14, 2020, 01:11:36 pm
And here's the command I'm using to build and the output:

Code: [Select]
avr-g++ -Ilib -I/usr/share/arduino/libraries/SoftwareSerial -I/usr/share/arduino/hardware/arduino/cores/arduino -g -Os -mmcu=atmega328p -Wall *.cpp -o example.bin -DF_CPU=8000000L

So, without resorting to some premade tool like arduino-mk, how would I make this work manually? I want to learn how this works, not just make it work for the sake of working.

Thanks!

I am also new to Arduino, so this may not the right way but I was able to build your code with no error.

I just tired copying your code to main.c main.cpp with
Code: [Select]
#vi main.cpp

and then create a Makefile with
Code: [Select]
BOARD_TAG ?= pro328
TARGET = atmega328
CFLAGS += -g -Wall
MONITOR_PORT ?= (for your machine port)
ARDUINO_LIBS ?= SoftwareSerial
include /usr/share/arduino/Arduino.mk

and then type
Code: [Select]
# make

then it will generates binaries under `build-pro328` directory from your main.cpp.

Title: Re: I don't understand libraries in C++
Post by: fanOfeeDIY on August 14, 2020, 01:15:55 pm
I tried them after on Ubuntu with installing
Code: [Select]
# sudo apt install arduino arduino-mk
Title: Re: I don't understand libraries in C++
Post by: fanOfeeDIY on August 14, 2020, 04:08:30 pm
So, without resorting to some premade tool like arduino-mk, how would I make this work manually? I want to learn how this works, not just make it work for the sake of working.

I missed reading this sentence, let me try build your code without using arduino-mk.
Title: Re: I don't understand libraries in C++
Post by: fanOfeeDIY on August 15, 2020, 03:45:25 am
I was able build using the libcore.a from the above procedure.

I am writing while I am also learning how the Arduino build works.

This is the Makefile I created:
Code: [Select]
AVR_FREQ ?= 8000000L
MCU ?= atmega328p

CC = avr-gcc
CXX = avr-g++
OBJCOPY = avr-objcopy
CFLAGS += -g -Wall -DF_CPU=$(AVR_FREQ) -mmcu=$(MCU)

MONITOR_PORT ?= `echo your serial port on pc`
ARDUINO_LIBS ?= SoftwareSerial

INCLUDES := -I/usr/share/arduino/libraries/SoftwareSerial \
        -I/usr/share/arduino/hardware/arduino/cores/arduino \
        -I/usr/share/arduino/hardware/arduino/variants/standard

SRC := main.cpp
TARGET := example
BIN := $(TARGET).bin
HEX := $(TARGET).hex

all: $(BIN) $(HEX)

$(BIN): $(SRC)
        $(CXX) $(INCLUDES) $(CFLAGS) $^ build-pro328/libcore.a -o $@

$(HEX): $(BIN)
        $(OBJCOPY) -O ihex $< $@

upload: $(HEX)
        avrdude -v -c arduino -p $(MCU) -P $(MONITOR_PORT) -b 115200 -U flash:w:$<

clean:
        rm -f $(BIN) $(HEX)

Then type:
Code: [Select]
$ make

will generate example.bin and example.hex.

Two things I felft burdon for this manual makefile method.

First is that you have to add the path to include for libraries you use, such as
/usr/share/arduino/libraries/SoftwareSerial
to INCLUDE everytime for using other libraries. Those libraries are in /usr/share/arduino/libraries.

Second, building libcore.a without arduino-mk do now look easy.
But let me investigte later.

Would this help?

Title: Re: I don't understand libraries in C++
Post by: fanOfeeDIY on August 15, 2020, 04:32:58 am
I got them to build together with libcore.a <- not working, user my later comment.
[EDIT] it was still using build-pro328/libcore.a which was built before with arduino-mk


The Makefile became bloated.

Code: [Select]
AVR_FREQ ?= 8000000L
MCU ?= atmega328p

CC = avr-gcc
CXX = avr-g++
OBJCOPY = avr-objcopy
AR = avr-ar
CFLAGS += -g -Wall -DF_CPU=$(AVR_FREQ) -mmcu=$(MCU)

MONITOR_PORT ?= `echo your serial port on pc`
ARDUINO_LIBS ?= SoftwareSerial

INCLUDES := -I/usr/share/arduino/libraries/SoftwareSerial \
        -I/usr/share/arduino/hardware/arduino/cores/arduino \
        -I/usr/share/arduino/hardware/arduino/variants/standard

SRC := main.cpp
TARGET := example
BIN := $(TARGET).bin
HEX := $(TARGET).hex

ARDUINO_DIR = /usr/share/arduino
ARDUINO_CORE_PATH = /usr/share/arduino/hardware/arduino/cores/arduino
ARDUINO_VAR_PATH = /usr/share/arduino/hardware/arduino/variants
VARIANT = standard
OBJDIR = .
CORE_LIB = $(OBJDIR)/libcore.a

all: $(BIN) $(HEX)

$(BIN): $(SRC) $(CORE_LIB)
        $(CXX) $(INCLUDES) $(CFLAGS) $^ build-pro328/libcore.a -o $@

$(HEX): $(BIN)
        $(OBJCOPY) -O ihex $< $@

upload: $(HEX)
        avrdude -v -c arduino -p $(MCU) -P $(MONITOR_PORT) -b 115200 -U flash:w:$<

clean:
        rm -f $(BIN) $(HEX)

## Bellow here is to build libcore.a

SYS_INCLUDES        := $(foreach lib, $(SYS_LIBS),  $(call get_library_includes,$(lib)))
USER_INCLUDES       := $(foreach lib, $(USER_LIBS), $(call get_library_includes,$(lib)))
PLATFORM_INCLUDES     := $(foreach lib, $(PLATFORM_LIBS), $(call get_library_includes,$(lib)))

CPPFLAGS += -mmcu=$(MCU) -DF_CPU=$(AVR_FREQ) -D__PROG_TYPES_COMPAT__ \
        -I$(ARDUINO_CORE_PATH) -I$(ARDUINO_VAR_PATH)/$(VARIANT) \
        $(SYS_INCLUDES) $(PLATFORM_INCLUDES) $(USER_INCLUDES) -Wall -ffunction-sections \
        -fdata-sections

CORE_C_SRCS     = $(wildcard $(ARDUINO_CORE_PATH)/*.c)
CORE_C_SRCS    += $(wildcard $(ARDUINO_CORE_PATH)/avr-libc/*.c)
CORE_CPP_SRCS   = $(wildcard $(ARDUINO_CORE_PATH)/*.cpp)
CORE_AS_SRCS    = $(wildcard $(ARDUINO_CORE_PATH)/*.S)

CORE_OBJ_FILES  = $(CORE_C_SRCS:.c=.c.o) $(CORE_CPP_SRCS:.cpp=.cpp.o) $(CORE_AS_SRCS:.S=.S.o)

MKDIR   = mkdir -p

$(OBJDIR)/core/%.c.o: $(ARDUINO_CORE_PATH)/%.c $(COMMON_DEPS) | $(OBJDIR)
        @$(MKDIR) $(dir $@)
        $(CC) -MMD -c $(CPPFLAGS) $(CFLAGS) $< -o $@

$(OBJDIR)/core/%.cpp.o: $(ARDUINO_CORE_PATH)/%.cpp $(COMMON_DEPS) | $(OBJDIR)
        @$(MKDIR) $(dir $@)
        $(CXX) -MMD -c $(CPPFLAGS) $(CXXFLAGS) $< -o $@

$(OBJDIR)/core/%.S.o: $(ARDUINO_CORE_PATH)/%.S $(COMMON_DEPS) | $(OBJDIR)
        @$(MKDIR) $(dir $@)
        $(CC) -MMD -c $(CPPFLAGS) $(ASFLAGS) $< -o $@

CORE_OBJS = $(patsubst $(ARDUINO_CORE_PATH)/%, $(OBJDIR)/core/%,$(CORE_OBJ_FILES))

LIB_OBJS = $(patsubst $(ARDUINO_LIB_PATH)/%.c,$(OBJDIR)/libs/%.c.o,$(LIB_C_SRCS)) \
        $(patsubst $(ARDUINO_LIB_PATH)/%.cpp,$(OBJDIR)/libs/%.cpp.o,$(LIB_CPP_SRCS)) \
        $(patsubst $(ARDUINO_LIB_PATH)/%.S,$(OBJDIR)/libs/%.S.o,$(LIB_AS_SRCS))

USER_LIB_OBJS = $(patsubst $(USER_LIB_PATH)/%.cpp,$(OBJDIR)/userlibs/%.cpp.o,$(USER_LIB_CPP_SRCS)) \
        $(patsubst $(USER_LIB_PATH)/%.c,$(OBJDIR)/userlibs/%.c.o,$(USER_LIB_C_SRCS)) \
        $(patsubst $(USER_LIB_PATH)/%.S,$(OBJDIR)/userlibs/%.S.o,$(USER_LIB_AS_SRCS))

PLATFORM_LIB_OBJS := $(patsubst $(ARDUINO_PLATFORM_LIB_PATH)/%.cpp,$(OBJDIR)/platformlibs/%.cpp.o,$(PLATFORM_LIB_CPP_SRCS)) \
        $(patsubst $(ARDUINO_PLATFORM_LIB_PATH)/%.c,$(OBJDIR)/platformlibs/%.c.o,$(PLATFORM_LIB_C_SRCS)) \
        $(patsubst $(ARDUINO_PLATFORM_LIB_PATH)/%.S,$(OBJDIR)/platformlibs/%.S.o,$(PLATFORM_LIB_AS_SRCS))

$(CORE_LIB): $(CORE_OBJS) $(LIB_OBJS) $(PLATFORM_LIB_OBJS) $(USER_LIB_OBJS)
        $(AR) rcs $@ $(CORE_OBJS) $(LIB_OBJS) $(PLATFORM_LIB_OBJS) $(USER_LIB_OBJS)

Then it will have:
Code: [Select]
$ ls
core  example.bin  example.hex  libcore.a  main.cpp  Makefile
Title: Re: I don't understand libraries in C++
Post by: fanOfeeDIY on August 15, 2020, 04:39:35 am
I will stop here working this. It was my fun for my summer holiday.

Probably good to optimize CFLAGS and CPPFLAGS in Makefile.

Would like to know `make upload` would work or not.
Title: Re: I don't understand libraries in C++
Post by: fanOfeeDIY on August 15, 2020, 06:47:37 am
This is the fixed Makefile for building without arduino-mk.

However, how to build libcore.a manually is highly reverse engineered from /usr/share/arduino/Arduino.mk.

Please convert the spaces to tab of indentation inside the Makefile when copying bellow.

Code: [Select]
SHELL = /bin/bash -xue

AVR_FREQ ?= 8000000L
MCU ?= atmega328p

CC = avr-gcc
CXX = avr-g++
OBJCOPY = avr-objcopy
AR = avr-gcc-ar
CFLAGS += -g -Wall -DF_CPU=$(AVR_FREQ) -mmcu=$(MCU)

MONITOR_PORT ?= `echo your serial port on pc`
ARDUINO_LIBS ?= SoftwareSerial

INCLUDES := -I/usr/share/arduino/libraries/SoftwareSerial \
        -I/usr/share/arduino/hardware/arduino/cores/arduino \
        -I/usr/share/arduino/hardware/arduino/variants/standard

SRC := main.cpp
TARGET := example
BIN := $(TARGET).bin
HEX := $(TARGET).hex

ARDUINO_DIR = /usr/share/arduino
ARDUINO_CORE_PATH = /usr/share/arduino/hardware/arduino/cores/arduino
ARDUINO_VAR_PATH = /usr/share/arduino/hardware/arduino/variants
VARIANT = standard
OBJDIR = .
CORE_LIB = $(OBJDIR)/libcore.a

all: $(CORE_LIB) $(BIN) $(HEX)

$(BIN): $(SRC) $(CORE_LIB)
        $(CXX) $(INCLUDES) $(CFLAGS) $^ -o $@

$(HEX): $(BIN)
        $(OBJCOPY) -O ihex $< $@

upload: $(HEX)
        avrdude -v -c arduino -p $(MCU) -P $(MONITOR_PORT) -b 115200 -U flash:w:$<

clean:
        rm -fr $(BIN) $(HEX) $(CORE_LIB) $(OBJDIR)/core $(OBJDIR)/libs $(OBJDIR)/platformlibs

## Bellow here is to build libcore.a

CFLAGS_STD = -std=gnu11 -flto -fno-fat-lto-objects
CXXFLAGS_STD = -fpermissive -fno-exceptions -std=gnu++11 -fno-threadsafe-statics -flto

LOCAL_C_SRCS    ?= $(wildcard *.c)
LOCAL_CPP_SRCS  ?= $(wildcard *.cpp)
LOCAL_CC_SRCS   ?= $(wildcard *.cc)
LOCAL_PDE_SRCS  ?= $(wildcard *.pde)
LOCAL_INO_SRCS  ?= $(wildcard *.ino)
LOCAL_AS_SRCS   ?= $(wildcard *.S)
LOCAL_SRCS      = $(LOCAL_C_SRCS)   $(LOCAL_CPP_SRCS) \
                $(LOCAL_CC_SRCS)   $(LOCAL_PDE_SRCS) \
                $(LOCAL_INO_SRCS) $(LOCAL_AS_SRCS)

ARDUINO_LIB_PATH = $(ARDUINO_DIR)/libraries

ARDUINO_LIBS += $(filter $(notdir $(wildcard $(ARDUINO_DIR)/libraries/*)), \
        $(shell sed -ne 's/^ *\# *include *[<\"]\(.*\)\.h[>\"]/\1/p' $(LOCAL_SRCS)))

USER_LIBS      := $(sort $(wildcard $(patsubst %,$(USER_LIB_PATH)/%,$(ARDUINO_LIBS))))
USER_LIB_NAMES := $(patsubst $(USER_LIB_PATH)/%,%,$(USER_LIBS))

SYS_LIBS       := $(sort $(wildcard $(patsubst %,$(ARDUINO_LIB_PATH)/%,$(filter-out $(USER_LIB_NAMES),$(ARDUINO_LIBS)))))
SYS_LIB_NAMES  := $(patsubst $(ARDUINO_LIB_PATH)/%,%,$(SYS_LIBS))

get_library_includes = $(if $(and $(wildcard $(1)/src), $(wildcard $(1)/library.properties)), \
        -I$(1)/src, \
        $(addprefix -I,$(1) $(wildcard $(1)/utility)))

SYS_INCLUDES        := $(foreach lib, $(SYS_LIBS),  $(call get_library_includes,$(lib)))
USER_INCLUDES       := $(foreach lib, $(USER_LIBS), $(call get_library_includes,$(lib)))

CPPFLAGS += -mmcu=$(MCU) -DF_CPU=$(AVR_FREQ) -D__PROG_TYPES_COMPAT__ \
        -I$(ARDUINO_CORE_PATH) -I$(ARDUINO_VAR_PATH)/$(VARIANT) \
        $(SYS_INCLUDES) $(PLATFORM_INCLUDES) $(USER_INCLUDES) -Wall -ffunction-sections \
        -fdata-sections -Os -fpermissive -fno-exceptions -std=gnu++11 -fno-threadsafe-statics -flto

CORE_C_SRCS     = $(wildcard $(ARDUINO_CORE_PATH)/*.c)
CORE_C_SRCS    += $(wildcard $(ARDUINO_CORE_PATH)/avr-libc/*.c)
CORE_CPP_SRCS   = $(wildcard $(ARDUINO_CORE_PATH)/*.cpp)
CORE_AS_SRCS    = $(wildcard $(ARDUINO_CORE_PATH)/*.S)

CORE_OBJ_FILES  = $(CORE_C_SRCS:.c=.c.o) $(CORE_CPP_SRCS:.cpp=.cpp.o) $(CORE_AS_SRCS:.S=.S.o)

MKDIR   = mkdir -p

$(OBJDIR)/core/%.c.o: $(ARDUINO_CORE_PATH)/%.c $(COMMON_DEPS) | $(OBJDIR)
        @$(MKDIR) $(dir $@)
        $(CC) -MMD -c $(CPPFLAGS) $(CFLAGS_STD) $< -o $@

$(OBJDIR)/core/%.cpp.o: $(ARDUINO_CORE_PATH)/%.cpp $(COMMON_DEPS) | $(OBJDIR)
        @$(MKDIR) $(dir $@)
        $(CXX) -MMD -c $(CPPFLAGS) $(CXXFLAGS_STD) $< -o $@

$(OBJDIR)/core/%.S.o: $(ARDUINO_CORE_PATH)/%.S $(COMMON_DEPS) | $(OBJDIR)
        @$(MKDIR) $(dir $@)
        $(CC) -MMD -c $(CPPFLAGS) $(ASFLAGS) $< -o $@

CORE_OBJS = $(patsubst $(ARDUINO_CORE_PATH)/%, $(OBJDIR)/core/%,$(CORE_OBJ_FILES))

get_library_files  = $(if $(and $(wildcard $(1)/src), $(wildcard $(1)/library.properties)), \
        $(call rwildcard,$(1)/src/,*.$(2)), \
        $(wildcard $(1)/*.$(2) $(1)/utility/*.$(2)))

LIB_C_SRCS          := $(foreach lib, $(SYS_LIBS),  $(call get_library_files,$(lib),c))
LIB_CPP_SRCS        := $(foreach lib, $(SYS_LIBS),  $(call get_library_files,$(lib),cpp))
LIB_AS_SRCS         := $(foreach lib, $(SYS_LIBS),  $(call get_library_files,$(lib),S))
USER_LIB_CPP_SRCS   := $(foreach lib, $(USER_LIBS), $(call get_library_files,$(lib),cpp))
USER_LIB_C_SRCS     := $(foreach lib, $(USER_LIBS), $(call get_library_files,$(lib),c))
USER_LIB_AS_SRCS    := $(foreach lib, $(USER_LIBS), $(call get_library_files,$(lib),S))

LIB_OBJS = $(patsubst $(ARDUINO_LIB_PATH)/%.c,$(OBJDIR)/libs/%.c.o,$(LIB_C_SRCS)) \
        $(patsubst $(ARDUINO_LIB_PATH)/%.cpp,$(OBJDIR)/libs/%.cpp.o,$(LIB_CPP_SRCS)) \
        $(patsubst $(ARDUINO_LIB_PATH)/%.S,$(OBJDIR)/libs/%.S.o,$(LIB_AS_SRCS))

$(OBJDIR)/libs/%.c.o: $(ARDUINO_LIB_PATH)/%.c
        @$(MKDIR) $(dir $@)
        $(CC) -MMD -c $(CPPFLAGS) $(CFLAGS_STD) $< -o $@

$(OBJDIR)/libs/%.cpp.o: $(ARDUINO_LIB_PATH)/%.cpp
        @$(MKDIR) $(dir $@)
        $(CXX) -MMD -c $(CPPFLAGS) $(CXXFLAGS_STD) $< -o $@

$(OBJDIR)/libs/%.S.o: $(ARDUINO_LIB_PATH)/%.S
        @$(MKDIR) $(dir $@)
        $(CC) -MMD -c $(CPPFLAGS) $(ASFLAGS) $< -o $@

$(CORE_LIB): $(CORE_OBJS) $(LIB_OBJS) $(PLATFORM_LIB_OBJS)
        $(AR) rcs $@ $(CORE_OBJS) $(LIB_OBJS) $(PLATFORM_LIB_OBJS) $(USER_LIB_OBJS)


After the build, it should have these files.

Code: [Select]
$ ls
core         example.hex  libs      Makefile    example.bin  libcore.a    main.cpp
Title: Re: I don't understand libraries in C++
Post by: fanOfeeDIY on August 15, 2020, 07:02:45 am
Probably Arduino was not a good example of how the linking works on gcc/g++, since it uses customized libcore to build.

I have not run the binary on Arduino.
Happy to hear if anybody tries the Makehile.
Title: Re: I don't understand libraries in C++
Post by: fanOfeeDIY on August 21, 2020, 12:44:45 pm
Hi,

After three days of ongoing updates on the Makefile, I end up making a project of simple Makefiles for Arduino on AVR and STM32.

https://github.com/mcd500/arduino-commandliners/blob/master/README.md

Thanks, for the question in the forum!