EEVblog Electronics Community Forum
Products => Computers => Programming => Topic started 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:
#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:
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!
-
https://www.arduino.cc/en/Reference/SoftwareSerial (https://www.arduino.cc/en/Reference/SoftwareSerial)
-
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.
-
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?
-
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...)
-
And here's the command I'm using to build and the output:
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
#vi main.cpp
and then create a Makefile with
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
# make
then it will generates binaries under `build-pro328` directory from your main.cpp.
-
I tried them after on Ubuntu with installing
# sudo apt install arduino arduino-mk
-
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.
-
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:
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:
$ 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?
-
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.
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:
$ ls
core example.bin example.hex libcore.a main.cpp Makefile
-
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.
-
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.
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.
$ ls
core example.hex libs Makefile example.bin libcore.a main.cpp
-
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.
-
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!