续上一篇文章的内容。由CubeMX构建的Makefile工程只支持C和汇编的编译,而且FreeRTOS的代码也需要作为C代码编译。
  当我们想使用C++时,需要做一些准备并且修改Makefile;另外本文补充了一下DSP库的调用方式。参考了另外两位博主的文章:

FreeRTOS与C++

  FreeRTOS的代码是必须作为C语言编译的。C++可以调用C函数,而且也可以在C++的源文件里写C函数。但是C的源文件里不能调用C++相关的东西,比如在C源文件里创建一个类的对象是不行的。
  所以我觉得最好的做法是把任务的定义放到自己的C++源文件中,而在FreeRTOS的C文件中用“extern”声明任务函数。

  具体做法就是在用CubeMX创建Task的时候,把任务的函数都设置成external。这里的默认选项是既有声明又有实现;exteral选项就是只有一个带extern的任务函数声明;还有一个weak选项表示这个任务函数有一个默认的实现,但可以被外部的定义覆盖,类似于C++中的虚函数的重新定义。
  我一般会把CubeMX工程中的下面的设置勾上。这样不同的外设就会有一个单独的.c和.h文件。

  然后就可以自己创建一个C++源文件来定义这几个任务函数。

  任务函数的声明需要放在 extern “C” {} 中,然后就可以在任务函数中创建类的对象,调用成员函数等。
  就像下面这个形式,可以把一堆任务函数的声明都放在一个 extern “C” {} 中,后面逐个对这些任务函数进行定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
extern "C"
{
void BlinkTskFxn(void *argument);
...
}

...

void BlinkTskFxn(void *argument)
{
for(;;){
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
osDelay(500);
}
}

修改Makefile

  Makefile的修改可以参考前面提到的另一篇博文。总得来说就是新增了一套编译C++源文件的规则。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
CXX_SOURCES = \
...

ifdef GCC_PATH
CXX = $(GCC_PATH)/$(PREFIX)g++
...
else
CXX = $(PREFIX)g++
...
endif

CXXFLAGS = $(MCU) $(C_DEFS) $(C_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections -std=c++11

CXXFLAGS += -g -gdwarf-2

CXXFLAGS += -MMD -MP -MF"$(@:%.o=%.d)"

LDFLAGS = $(MCU) -specs=nano.specs -specs=nosys.specs -T$(LDSCRIPT) $(LIBDIR) $(LIBS) -Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref -Wl,--gc-sections

OBJECTS += $(addprefix $(BUILD_DIR)/,$(notdir $(CXX_SOURCES:.cpp=.o)))
vpath %.cpp $(sort $(dir $(CXX_SOURCES)))

$(BUILD_DIR)/%.o: %.cpp Makefile | $(BUILD_DIR)
$(CXX) -c $(CXXFLAGS) -Wa,-a,-ad,-alms=$(BUILD_DIR)/$(notdir $(<:.cpp=.lst)) $< -o $@

  这里的CXX_SOURCES用于列出需要编译的C++源文件;CXX表示编译器,CXXFLAGS中包含编译选项,其中沿用的C_DEFS和C_INCLUDES,这表示即使把编译器改成g++,之前gcc用的define和include路径还是相同的。
  LDFLAGS是link的选项,其中相比于之前的LDFLAGS新增了-specs=nosys.specs,这是必不可少的,如果没有的话编译就会报错,具体做了什么我没太搞明白。-specs=nano.specs表示采用精简后的C库,这个有无不会对编译是否能通过不会有影响。

  为了便于调试,也需要在工程的属性中为C++添加include和symbol(如果之前没有添加的话),这样就不会有那种报错的波浪线。
  在这里添加include路径和symbol的时候,有“Add to all languages”和“Add to all configurations”的选项可以勾选,这样对于一个条目就只需要添加一次就行了。不管是C还是C++,不管是Debug的配置还是Release的配置,就都能设置好,不需要再去逐个反复添加。(虽然对于Makefile工程来说没有Debug和Release的区分,优化等级也是直接改Makefile的)。

添加DSP库

  通过CubeMX添加DSP库真的非常快。参考前面提到的另一篇博文,在“Pinout & Configuration”界面点“Software Packs”选择组件。

  然后就可以安装DSP库并勾选它。

  这时候再回到CubeMX的工程就可以看到左边多了一栏Software Packs,选择它之后再把DSP Library勾选上,令当前的CubeMX工程支持DSP Library。

  通过这些配置生成的Makefile工程将另外生成DSP库的头文件和预编译好的lib文件,并放在Middlewares目录下。

  预编译好的lib文件有三种,开头的arm,iar,libarm应该是区分它们是在不同的环境下编译的。l后缀表示小端模式,f后缀表示使用FPU。
  我一开始尝试用arm_cortexM4lf_math.lib,编译会有warning,说这个库用的wchar_t类型是2字节的,而我们的工程中用的是4字节的,这不兼容。后来用iar_cortexM4lf_math.a就没有这个问题。
  关于DSP库的API,可以在官方文档里找到。如果是从源文件开始编译DSP库,还有一些宏定义可以设置,比如ARM_MATH_BIG_ENDIAN设置大端模式;ARM_MATH_MATRIX_CHECK设置输入输出矩阵的维度检查……但是像现在这样用已经编译好的库,这些宏定义是没用的。就像之前要从源码编译时,需要定义__FPU_PRESENT一样,现在即使不定义也没有关系,因为源码都已经编译完成了。
  用预先编译好的DSP库,就只需要include <arm_math.h>和定义ARM_MATH_CM7, ARM_MATH_CM4, ARM_MATH_CM3, ARM_MATH_CM0PLUS, ARM_MATH_CM0, ARM_MATH_ARMV8MBL, ARM_MATH_ARMV8MML中的一种就好了。

  我再回过头来发现,即使不在CubeMX中添加DSP库,这些DSP库相关的文件随着CMSIS的device驱动一起就提供了。就在Drivers/CMSIS/Lib目录下。

1
2
3
4
5
6
7
8
9
10
11
12
#defines
-DARM_MATH_CM4

#includes
C_INCLUDES = \
-IDrivers/CMSIS/DSP/Include

# libraries
LIBS = -lc -lm -lnosys \
-l:iar_cortexM4lf_math.a
LIBDIR = \
-LDrivers/CMSIS/Lib/IAR

  所以只需要在Makefile中增加器件相关的define和include相应的lib就行了,而不需要通过CubeMX额外导入DSP库。