Freertos, stm32, libopencm3, timer and linking issues

Hello all,
I m seeing a very very weird issue not being able to build freertos into my project.
I m using stm32f103, libopencm3 along with freertos. I have copied the FreeRTOSConfig.h file from the Cortex_STM32F103_GCC_Rowley folder.

My directory structure looks like so

.
├── ceedling
├── CHANGELOG.md
├── Dockerfile
├── project.yml
├── README.md
├── rules.mk
├── src
│   ├── adapters
│   │   ├── apiFreeRTOS.h
│   │   └── apiLibopenCM3.h
│   ├── device_drivers
│   │   ├── ddrvDisp.c
│   │   ├── ddrvDisp.h
│   │   └── Readme.md
│   ├── errorno.h
│   ├── FreeRTOSConfig.h
│   ├── main.c
│   ├── Makefile
│   ├── managers
│   │   ├── dispMan.c
│   │   ├── dispMan.h
│   │   └── Readme.md
│   ├── submodules
│   │   └── freertos
│   └── virtual_devices
│       ├── Readme.md
│       ├── vdevDisp.c
│       └── vdevDisp.h
├── submodules
│   ├── freertos
│   │   ├── cspell.config.yaml
│   │   ├── FreeRTOS
│   │   ├── FreeRTOS-Plus
│   │   ├── FreeRTOS+TCP.url
│   │   ├── GitHub-FreeRTOS-Home.url
│   │   ├── History.txt
│   │   ├── LICENSE.md
│   │   ├── manifest.yml
│   │   ├── Quick_Start_Guide.url
│   │   ├── README.md
│   │   ├── tools
│   │   └── Upgrading-to-FreeRTOS.url
│   └── libopencm3
│       ├── COPYING.GPL3
│       ├── COPYING.LGPL3
│       ├── doc
│       ├── HACKING
│       ├── HACKING_COMMON_DOC
│       ├── include
│       ├── ld
│       ├── lib
│       ├── locm3.sublime-project
│       ├── Makefile
│       ├── mk
│       ├── README.md
│       ├── scripts
│       └── tests
├── test
│   ├── device_drivers
│   │   └── test_ddrvDisp.c
│   ├── managers
│   │   └── test_dispMan.c
│   ├── support
│   └── virtual_devices
│       └── test_vdevDisp.c
└── vendor
    └── ceedling
        ├── bin
        ├── docs
        ├── lib
        ├── plugins
        └── vendor

The names of the folders indicate the purpose of the files inside.
The Makefile inside the src folder looks like this

PROJECT = aws
BUILD_DIR = binaries

DEVICE_DRIVERS = device_drivers
MANAGERS = managers 
VIRTUAL_DEVICES = virtual_devices
ADAPTERS = adapters

SOURCES_DEVICE_DRIVERS = $(wildcard device_drivers/*.c)
SOURCES_MANAGERS = $(wildcard managers/*.c)
SOURCES_VIRTUAL_DEVICES = $(wildcard virtual_devices/*.c)
SOURCES_ADAPTERS = $(wildcard adapters/*.c)

FREERTOS_SRC = ../submodules/freertos/FreeRTOS/Source
FREERTOS_HEADERS = ../submodules/freertos/FreeRTOS/Source/include

INCLUDES += -I$(FREERTOS_HEADERS)
INCLUDES += -I$(FREERTOS_SRC)/portable/GCC/ARM_CM3
INCLUDES += -I$(DEVICE_DRIVERS)
INCLUDES += -I$(MANAGERS)
INCLUDES += -I$(VIRTUAL_DEVICES)
INCLUDES += -I$(ADAPTERS)

TGT_CFLAGS	+= -I$(FREERTOS_HEADERS)
TGT_CXXFLAGS	+= -I$(FREERTOS_HEADERS)

# define TEST for compiledb to generate the lsp compilation database
#TEST?=0
#ifeq ($(TEST),1)
#TGT_CFLAGS += -DTEST
#TGT_CXXFLAGS += -DTEST
#endif

CFILES = main.c 
CFILES += $(SOURCES_DEVICE_DRIVERS) $(SOURCES_MANAGERS) $(SOURCES_ADAPTERS) $(SOURCES_VIRTUAL_DEVICES)
CFILES += $(FREERTOS_SRC)/portable/GCC/ARM_CM3/port.c  $(FREERTOS_SRC)/tasks.c  $(FREERTOS_SRC)/list.c $(FREERTOS_SRC)/queue.c $(FREERTOS_SRC)/portable/MemMang/heap_2.c $(FREERTOS_SRC)/timers.c

# TODO - you will need to edit these two lines!
DEVICE=stm32f103rct6
OOCD_FILE = target/stm32f1x.cfg

# You shouldn't have to edit anything below here.
VPATH += $(SHARED_DIR)
INCLUDES += $(patsubst %,-I%, . $(SHARED_DIR))
OPENCM3_DIR=../submodules/libopencm3

include $(OPENCM3_DIR)/mk/genlink-config.mk
include ../rules.mk
include $(OPENCM3_DIR)/mk/genlink-rules.mk

and the top level rules.mk file looks like so.

BUILD_DIR ?= binaries
OPT ?= -Os
CSTD ?= -std=c99

# Be silent per default, but 'make V=1' will show all compiler calls.
# If you're insane, V=99 will print out all sorts of things.
V?=0
ifeq ($(V),0)
Q	:= @
NULL	:= 2>/dev/null
endif

# Tool paths.
PREFIX	?= arm-none-eabi-
CC	= $(PREFIX)gcc
CXX	= $(PREFIX)g++
LD	= $(PREFIX)gcc
OBJCOPY	= $(PREFIX)objcopy
OBJDUMP	= $(PREFIX)objdump
OOCD	?= openocd

OPENCM3_INC = $(OPENCM3_DIR)/include

# Inclusion of library header files
INCLUDES += $(patsubst %,-I%, . $(OPENCM3_INC) )

OBJS = $(CFILES:%.c=$(BUILD_DIR)/%.o)
OBJS += $(CXXFILES:%.cxx=$(BUILD_DIR)/%.o)
OBJS += $(AFILES:%.S=$(BUILD_DIR)/%.o)
GENERATED_BINS = $(BUILD_DIR)/$(PROJECT).elf $(PROJECT).bin $(PROJECT).map $(PROJECT).list $(PROJECT).lss

TGT_CPPFLAGS += -MD
TGT_CPPFLAGS += -Wall -Wundef $(INCLUDES)
TGT_CPPFLAGS += $(INCLUDES) $(OPENCM3_DEFS)

TGT_CFLAGS += $(OPT) $(CSTD) -ggdb3
TGT_CFLAGS += $(ARCH_FLAGS)
TGT_CFLAGS += -fno-common
TGT_CFLAGS += -ffunction-sections -fdata-sections
TGT_CFLAGS += -Wextra -Wshadow -Wno-unused-variable -Wimplicit-function-declaration
TGT_CFLAGS += -Wredundant-decls -Wstrict-prototypes -Wmissing-prototypes

TGT_CXXFLAGS += $(OPT) $(CXXSTD) -ggdb3
TGT_CXXFLAGS += $(ARCH_FLAGS)
TGT_CXXFLAGS += -fno-common
TGT_CXXFLAGS += -ffunction-sections -fdata-sections
TGT_CXXFLAGS += -Wextra -Wshadow -Wredundant-decls  -Weffc++

TGT_ASFLAGS += $(OPT) $(ARCH_FLAGS) -ggdb3

TGT_LDFLAGS += -T$(LDSCRIPT) -L$(OPENCM3_DIR)/lib -nostartfiles
TGT_LDFLAGS += $(ARCH_FLAGS)
TGT_LDFLAGS += -specs=nano.specs
TGT_LDFLAGS += -Wl,--gc-sections
# OPTIONAL
TGT_LDFLAGS += -Wl,-Map=$(PROJECT).map
ifeq ($(V),99)
TGT_LDFLAGS += -Wl,--print-gc-sections
endif

# Linker script generator fills this in for us.
ifeq (,$(DEVICE))
LDLIBS += -l$(OPENCM3_LIB)
endif
# nosys is only in newer gcc-arm-embedded...
LDLIBS += -specs=nosys.specs
LDLIBS += -Wl,--start-group -lc -lgcc -lnosys -Wl,--end-group

# Burn in legacy hell fortran modula pascal yacc idontevenwat
.SUFFIXES:
.SUFFIXES: .c .S .h .o .cxx .elf .bin .list .lss

# Bad make, never *ever* try to get a file out of source control by yourself.
%: %,v
%: RCS/%,v
%: RCS/%
%: s.%
%: SCCS/s.%

all: $(PROJECT).elf $(PROJECT).bin
flash: $(PROJECT).flash

# error if not using linker script generator
ifeq (,$(DEVICE))
$(LDSCRIPT):
ifeq (,$(wildcard $(LDSCRIPT)))
    $(error Unable to find specified linker script: $(LDSCRIPT))
endif
else
# if linker script generator was used, make sure it's cleaned.
GENERATED_BINS += $(LDSCRIPT)
endif

# Need a special rule to have a bin dir
$(BUILD_DIR)/%.o: %.c
	@printf "  CC\t$<\n"
	@mkdir -p $(dir $@)
	$(Q)$(CC) $(TGT_CFLAGS) $(CFLAGS) $(TGT_CPPFLAGS) $(CPPFLAGS) -o $@ -c $<

$(BUILD_DIR)/%.o: %.cxx
	@printf "  CXX\t$<\n"
	@mkdir -p $(dir $@)
	$(Q)$(CXX) $(TGT_CXXFLAGS) $(CXXFLAGS) $(TGT_CPPFLAGS) $(CPPFLAGS) -o $@ -c $<

$(BUILD_DIR)/%.o: %.S
	@printf "  AS\t$<\n"
	@mkdir -p $(dir $@)
	$(Q)$(CC) $(TGT_ASFLAGS) $(ASFLAGS) $(TGT_CPPFLAGS) $(CPPFLAGS) -o $@ -c $<

$(PROJECT).elf: $(OBJS) $(LDSCRIPT) $(LIBDEPS)
	@printf "  LD\t$@\n"
	$(Q)$(LD) $(TGT_LDFLAGS) $(LDFLAGS) $(OBJS) $(LDLIBS) -o $(BUILD_DIR)/$@

%.bin: %.elf
	@printf "  OBJCOPY\t$@\n"
	$(Q)$(OBJCOPY) -O binary  $(BUILD_DIR)/$< $(BUILD_DIR)/$@

%.lss: %.elf
	$(OBJDUMP) -h -S $< > $@

%.list: %.elf
	$(OBJDUMP) -S $< > $@

%.flash: %.elf
	@printf "  FLASH\t$<\n"
ifeq (,$(OOCD_FILE))
	$(Q)(echo "halt; program $(realpath $(*).elf) verify reset" | nc -4 localhost 4444 2>/dev/null) || \
		$(OOCD) -f interface/$(OOCD_INTERFACE).cfg \
		-f target/$(OOCD_TARGET).cfg \
		-c "program $(realpath $(*).elf) verify reset exit" \
		$(NULL)
else
	$(Q)(echo "halt; program $(realpath $(*).elf) verify reset" | nc -4 localhost 4444 2>/dev/null) || \
		$(OOCD) -f $(OOCD_FILE) \
		-c "program $(realpath $(*).elf) verify reset exit" \
		$(NULL)
endif

clean:
	rm -rf $(BUILD_DIR) $(GENERATED_BINS)

.PHONY: all clean flash
-include $(OBJS:.o=.d)

When I try to run make -C src/ in the project folder I see this error.

root@3bc5b3869e43:/home# make -C src/
make: Entering directory '/home/src'
  CC    main.c
  CC    device_drivers/ddrvDisp.c
  CC    managers/dispMan.c
  CC    virtual_devices/vdevDisp.c
  GENLNK  stm32f103rct6
  LD    aws.elf
/usr/local/bin/arm_gcc/gcc-arm-none-eabi-10.3-2021.10/bin/../lib/gcc/arm-none-eabi/10.3.1/../../../../arm-none-eabi/bin/ld: binaries/device_drivers/ddrvDisp.o: in function `initDisplayGPIO':
/home/src/device_drivers/ddrvDisp.c:77: undefined reference to `xTimerCreate'
/usr/local/bin/arm_gcc/gcc-arm-none-eabi-10.3-2021.10/bin/../lib/gcc/arm-none-eabi/10.3.1/../../../../arm-none-eabi/bin/ld: /home/src/device_drivers/ddrvDisp.c:78: undefined reference to `xTimerCreate'
/usr/local/bin/arm_gcc/gcc-arm-none-eabi-10.3-2021.10/bin/../lib/gcc/arm-none-eabi/10.3.1/../../../../arm-none-eabi/bin/ld: /home/src/device_drivers/ddrvDisp.c:79: undefined reference to `xTimerGenericCommandFromTask'
/usr/local/bin/arm_gcc/gcc-arm-none-eabi-10.3-2021.10/bin/../lib/gcc/arm-none-eabi/10.3.1/../../../../arm-none-eabi/bin/ld: /home/src/device_drivers/ddrvDisp.c:80: undefined reference to `xTimerGenericCommandFromTask'
/usr/local/bin/arm_gcc/gcc-arm-none-eabi-10.3-2021.10/bin/../lib/gcc/arm-none-eabi/10.3.1/../../../../arm-none-eabi/bin/ld: binaries/device_drivers/ddrvDisp.o: in function `setDisplayState':
/home/src/device_drivers/ddrvDisp.c:96: undefined reference to `xTimerGenericCommandFromTask'
/usr/local/bin/arm_gcc/gcc-arm-none-eabi-10.3-2021.10/bin/../lib/gcc/arm-none-eabi/10.3.1/../../../../arm-none-eabi/bin/ld: /home/src/device_drivers/ddrvDisp.c:97: undefined reference to `xTimerGenericCommandFromTask'
/usr/local/bin/arm_gcc/gcc-arm-none-eabi-10.3-2021.10/bin/../lib/gcc/arm-none-eabi/10.3.1/../../../../arm-none-eabi/bin/ld: /home/src/device_drivers/ddrvDisp.c:98: undefined reference to `xTimerGenericCommandFromTask'
/usr/local/bin/arm_gcc/gcc-arm-none-eabi-10.3-2021.10/bin/../lib/gcc/arm-none-eabi/10.3.1/../../../../arm-none-eabi/bin/ld: binaries/device_drivers/ddrvDisp.o:/home/src/device_drivers/ddrvDisp.c:99: more undefined references to `xTimerGenericCommandFromTask' follow
collect2: error: ld returned 1 exit status
make: *** [../rules.mk:145: aws.elf] Error 1
make: Leaving directory '/home/src'
root@3bc5b3869e43:/home#

The top of the ddrvDisp.h files contains these includes

#include "stdint.h"
#include "stddef.h"
#include "errorno.h"
#include "apiLibopenCM3.h"
#include "apiFreeRTOS.h"

and the contents of apiFreeRTOS.h are so

#ifndef APIFREERTOS_H
#define APIFREERTOS_H

#ifndef TEST

#include "FreeRTOS.h"
#include "FreeRTOSConfig.h"
#include "task.h"
#include "queue.h"
#include "timers.h"

#else 

#include "stdint.h"

#define pdTRUE 1
#define pdFALSE 0
#define pdMS_TO_TICKS(x) (x)
typedef uint8_t QueueHandle_t;
typedef uint8_t TimerHandle_t;
typedef uint32_t UBaseType_t;
typedef uint32_t BaseType_t;
typedef uint32_t TickType_t;
typedef void (*TimerCallbackFunction_t) (void*);

QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize );
QueueHandle_t xQueueReceive( QueueHandle_t QHandle, void *pvBuffer, TickType_t waitTicks);
QueueHandle_t xQueueSend( QueueHandle_t QHandle, void *pvBuffer, TickType_t waitTicks);
BaseType_t xTimerStart(TimerHandle_t xTimer,TickType_t xTicksToWait);
TimerHandle_t xTimerCreate(const char *const pcTimerName, const TickType_t xTimerPeriodInTicks, const BaseType_t xAutoReload, void *const pvTimerID, TimerCallbackFunction_t pxCallbackFunction);
BaseType_t xTimerReset( TimerHandle_t xTimer, TickType_t xBlockTime );
BaseType_t xTimerChangePeriod( TimerHandle_t xTimer, TickType_t xNewPeriod, TickType_t xBlockTime );

#endif

#endif // !APIFREERTOS_H

The apiFreeRTOS.h file is just a shim to allow for mocking during unit testing. if TEST is not defined it simply includes the actual freertos headers. But I digress…

For normal compilation, the apiFreeRTOS includes the appropriate timer header and in the makefile the timers.c file is being includes as a list of source files

CFILES = main.c 
CFILES += $(SOURCES_DEVICE_DRIVERS) $(SOURCES_MANAGERS) $(SOURCES_ADAPTERS) $(SOURCES_VIRTUAL_DEVICES)
CFILES += $(FREERTOS_SRC)/portable/GCC/ARM_CM3/port.c  $(FREERTOS_SRC)/tasks.c  $(FREERTOS_SRC)/list.c $(FREERTOS_SRC)/queue.c $(FREERTOS_SRC)/portable/MemMang/heap_2.c $(FREERTOS_SRC)/timers.c

I m not sure why I m seeing the undefined reference to error… I was hoping that someone else can see what I m not seeing clearly here and help me fix this issue.
I tried to keep the post length short and skipped over some details that I felt were adding noise. Please do let me know if I need to provide any more information to help you guys help me. :slight_smile:

Thanks in advance ! :smiley:

Okay… I realised that the Timer functionality depended on configSUPPORT_DYNAMIC_ALLOCATION to be defined as 1 in order for the timer functions to be enabled.

I added that macro in the FreeRTOSConfig.h and … nothing. I still see the same undefined reference to error when I try to build.

PS : also the defines about enabling timers in the FreeRTOSConfig.h. i.e

#define configUSE_TIMERS            1
#define configTIMER_TASK_PRIORITY (configMAX_PRIORITIES - 1)
#define configTIMER_QUEUE_LENGTH          10
#define configTIMER_TASK_STACK_DEPTH      configMINIMAL_STACK_SIZE

Try adding $(info CFILES: $(CFILES)) and $(info OBJS: $(OBJS)) to your makefile and/or make -d to see why the FreeRTOS sources are not compiled and linked for the ‘elf’ target. That’s the reason for the undefined references.

Thats a neat tip… I did not know about the info keyword in make…
I tried what you suggested and this is what they print to the terminal

CFILES:main.c  ../submodules/freertos/FreeRTOS/Source/portable/GCC/ARM_CM3/port.c  ../submodules/freertos/FreeRTOS/Source/tasks.c  ../submodules/freertos/FreeRTOS/Source/list.c ../submodules/freertos/FreeRTOS/Source/queue.c ../submodules/freertos/FreeRTOS/Source/portable/MemMang/heap_2.c ../submodules/freertos/FreeRTOS/Source/timers.c  device_drivers/ddrvDisp.c managers/dispMan.c  virtual_devices/vdevDisp.c
OBJS:binaries/main.o binaries/../submodules/freertos/FreeRTOS/Source/portable/GCC/ARM_CM3/port.o binaries/../submodules/freertos/FreeRTOS/Source/tasks.o binaries/../submodules/freertos/FreeRTOS/Source/list.o binaries/../submodules/freertos/FreeRTOS/Source/queue.o binaries/../submodules/freertos/FreeRTOS/Source/portable/MemMang/heap_2.o binaries/../submodules/freertos/FreeRTOS/Source/timers.o binaries/device_drivers/ddrvDisp.o binaries/managers/dispMan.o binaries/virtual_devices/vdevDisp.o

so the sources and the objects are correct… but I still see the error…
and I can confirm that I see the object files in the respective directories.
any suggestions on how to troubleshoot this further ??

Crap!!!
It was a path issue…
The BUILD_DIR variable in the makefile is specified wrt to the src directory…
This is presently set to binaries
This is breaking the compilation.
I checked this variables value from an earlier commit and it was set to ../build/binaries
Setting the value of BUILD_DIR to this earlier value fixes the issue and I am able to compile the final binary properly. Also, the ephemeral object files are organized in a more suitable manner, out of tree.
I ll close the issue now. Thank you @hs2 . I picked up a new trick and your suggestion was super helpful in confirming that I was looking at a linker issue and not a missing source or compilation issue… :slight_smile:

1 Like