Не давно я писал статью про то как настроить рабочее окружение для написания кода на СИ для работы в редакторе VScode. Но что делать если мы хотим писать код на С++, а не на СИ? На самом деле ответ на этот вопрос не тривиален. CubeMX генерирует проект, и параметры сборки таким образом что там используются только си файлы. Это значит что нам необходимо сделать 2 вещи - это соединить сишный код сгенерированный кубом с нашим плюсовым кодом и дописать make файл чтобы он мог собирать дополнительные с++ файлы.
Для экономии времени я предлагаю взять проект который получился у нас при настройке vscode в прошлый раз. На его примере мы рассмотрим добавление в проект поддержки с++ создав простой класс blinker методы которого будут мигать диодам.
Соединяем CPP и C
Тут необходимо отметь что этот пункт также будет работать в IDE такой как CubeIDE и ей подобных. Притом для работы с С++ в них нужно будет только поставить галочку при создании проекта о поддержке с++ и прописать пути к созданным вами файлам.
Для начала я предлагаю создать папку App
в коре проекта, чтобы поместить туда все cpp файлы что позволит нам явно разделить сишную и плюсовую часть проекта. Далее внутри этой папки создать Inc
и Src
, и поместить туда хедеры и cpp файлы соответственно. Это нужно для сохранения стилистики кода сгенерированного в CubeMX.
mkdir App App/Inc App/Src
Далее я предлагаю создать файл app.cpp
и app.h
в которых будет описана функция app()
, которая будет вызываться в main
перед while(1) {}
и по сути станет для нас плюсовой версией функции main
.
app.h:
#ifndef INC_APP_H_
#define INC_APP_H_
#ifdef __cplusplus
extern "C" {
#endif
void app_c(void);
#ifdef __cplusplus
}
#endif
void app(void);
#endif /* INC_APP_H_ */
app.cpp:
#include "app.h"
#include "main.h"
#include "blink.h"
BLINK blink;
extern "C" void app_c(void) {
app();
}
void app(void) {
while (true) {
blink.toggle();
}
}
Далее создадим 2 файла blink.h
и blink.c
, в них будет описан класс BLINK
метод которого и будет мигать диодом:
blink.h
#ifndef INC_BLINK_H_
#define INC_BLINK_H_
class BLINK {
public:
void toggle();
};
#endif /* INC_BLINK_H_ */
blink.cpp
#include "blink.h"
#include "gpio.h"
void BLINK::toggle(){
HAL_GPIO_TogglePin(LD_D_GPIO_Port, LD_D_Pin);
}
Далее я предлагаю чуть подробнее рассмотреть что мы написали:
- Конструкция в начале каждого хедера нужна для того чтобы избежать повторных добавления, вместо нее можно использовать
#pragma once
.
#ifndef INC_BLINK_H_
#define INC_BLINK_H_
#endif /* INC_BLINK_H_ */
- Данный код нужен для того чтобы g++ собрал
app_c()
как сишную функцию, при этом при добавлении этого хедера в*.c
файл, это функция будет рассматриваться как обычная:
#ifdef __cplusplus
extern "C" {
#endif
void app_c(void);
#ifdef __cplusplus
}
#endif
Без extern "C" {}
функция собралась бы как плюсовая и её нельзя было бы вызвать из си кода. Поэтому попытка вызова app()
(расположенной ниже) в си коде вызовет ошибку.
- В
*.cpp
файле функциюapp_c()
тоже необходимо обернуть вextern "C"
но тут не нужны никакие#ifdef
потому что этот файл будет собираться толькоg++
.
extern "C" void app_c(void) {
app();
}
- Дальше все как обычно, расписываем функцию
app()
и создаем экземпляр классаBLINK
. - Для того чтобы добавить СИ код в С++ все достаточно просто, добавляем хедер, и вызываем функцию в методе и все:
#include "gpio.h"
void BLINK::toggle(){
HAL_GPIO_TogglePin(LD_D_GPIO_Port, LD_D_Pin);
}
И в конце нужно добавить вызов нашей app_c()
из main.c
файла. Для этого добавляем хедер:
/* USER CODE BEGIN Includes */
#include "app.h"
/* USER CODE END Includes */
А также добавляем сам вызов после инициализации CubeIDE:
/* USER CODE BEGIN WHILE */
app_c();
while (1)
{
/* USER CODE END WHILE */
MAKE
После того как мы написали код, было бы не плохо разобраться с тем как его собрать, для этого нам нужно будет добавить описание сборки для с++ файлов. Для этого будем разбирать сгенерированный make
попутно делая в нем правки.
Для начала посмотрим на один из верхних блоков, править его сейчас не нужно, но если мы будем готовить версию к релизу нужно будет выставить 0 и -2g соответственно.
######################################
# building variables
######################################
# debug build?
DEBUG = 1
# optimization
OPT = -Og
Далее блок сo ссылками на исходники программы:
######################################
# source
######################################
# C sources
C_SOURCES = \
Core/Src/main.c \
Ниже нам нужно будет добавить свои CPP sources
:
# CPP sources
CPP_SOURCES = \
App/Src/app.cpp \
App/Src/blink.cpp
После идет блок с сокращенными названиями, в него нужно добавить блок с CXX
:
ifdef GCC_PATH
CC = $(GCC_PATH)/$(PREFIX)gcc
CXX = $(GCC_PATH)/$(PREFIX)g++
AS = $(GCC_PATH)/$(PREFIX)gcc -x assembler-with-cpp
CP = $(GCC_PATH)/$(PREFIX)objcopy
SZ = $(GCC_PATH)/$(PREFIX)size
else
CC = $(PREFIX)gcc
CXX = $(PREFIX)g++
AS = $(PREFIX)gcc -x assembler-with-cpp
CP = $(PREFIX)objcopy
SZ = $(PREFIX)size
endif
HEX = $(CP) -O ihex
BIN = $(CP) -O binary -S
Потом идут CFLAGS
, параметры контроллера в них трогать не нужно, а вот в C_INCLUDES
будет необходимо добавить папку с нашими хедерами:
# C includes
C_INCLUDES = \
-IApp/Inc \
-ICore/Inc \
...
После чего, для каждого CFLAGS
нужно будет сделать почти такой же CPPFLAGS
, только вместо -std=c11
, должно быть -std=c++17
:
# compile gcc flags
ASFLAGS = $(MCU) $(AS_DEFS) $(AS_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections
CFLAGS = $(MCU) $(C_DEFS) $(C_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections -std=c11
CPPFLAGS = $(MCU) $(C_DEFS) $(C_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections -std=c++17
ifeq ($(DEBUG), 1)
CFLAGS += -g -gdwarf-2
CPPFLAGS += -g -gdwarf-2
endif
# Generate dependency information
CFLAGS += -MMD -MP -MF"$(@:%.o=%.d)"
CPPFLAGS += -MMD -MP -MF"$(@:%.o=%.d)"
Далее идет блок с параметрами линковщика, тут нужно добавить 2 флага -specs=nosys.specs -lstdc++
в LDFLAGS
, для избежания проблем со стандартной библиотекой. В итоге должно получится так:
LDFLAGS = $(MCU) -specs=nano.specs -T$(LDSCRIPT) $(LIBDIR) $(LIBS) -Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref -Wl,--gc-sections -specs=nosys.specs -lstdc++
После чего идет описание сборки кода, где нам необходимо добавить объектники которые соберутся с++:
#######################################
# build the application
#######################################
# list of objects
OBJECTS = $(addprefix $(BUILD_DIR)/,$(notdir $(C_SOURCES:.c=.o)))
vpath %.c $(sort $(dir $(C_SOURCES)))
# list of c++ objects
OBJECTS += $(addprefix $(BUILD_DIR)/,$(notdir $(CPP_SOURCES:.cpp=.o)))
vpath %.cpp $(sort $(dir $(CPP_SOURCES)))
# list of ASM program objects
OBJECTS += $(addprefix $(BUILD_DIR)/,$(notdir $(ASM_SOURCES:.s=.o)))
vpath %.s $(sort $(dir $(ASM_SOURCES)))
И наконец нужно будет собрать все эти обьектники, где тоже необходимо добавить описание сборки файлов с++:
$(BUILD_DIR)/%.o: %.c Makefile | $(BUILD_DIR)
$(CC) -c $(CFLAGS) -Wa,-a,-ad,-alms=$(BUILD_DIR)/$(notdir $(<:.c=.lst)) $< -o $@
$(BUILD_DIR)/%.o: %.cpp Makefile | $(BUILD_DIR)
$(CXX) -c $(CPPFLAGS) -Wa,-a,-ad,-alms=$(BUILD_DIR)/$(notdir $(<:.cpp=.lst)) $< -o $@
$(BUILD_DIR)/%.o: %.s Makefile | $(BUILD_DIR)
$(AS) -c $(CFLAGS) $< -o $@
После чего идет блок линковщика и в нем уже ничего править не нужно.
Пример настроенного проекта доступен тут.