这两天折腾了一些不是那么有用的东西,用CMake构建CCS工程,以及编译EMCV Library(嵌入式计算机视觉库)。为什么说不是那么有用呢?因为虽然可以用CMake编译TI的CCS工程,但是调试还是离不开CCS。用CMake只是让程序编写的开发环境变得轻便了,可以在VS Code的界面下完成代码编写和编译,但最后Emulation还是得用CCS。另外,EMCV是OpenCV1.x移植到C6000 DSP上的计算机视觉库,一开始我没注意OpenCV的版本,原以为现有的软件算法能够比较方便地移植到DSP上,但是OpenCV1.x基本已经没有人用了,数据类型的定义和OpenCV3有较大的差别,所以我暂时还没有用上这个库,只是编译了一下。

CMake入门

  我现在用的CMake是3.22.1版本,网上有很多CMake的教程,官网的教程挺好的,从建立工程,到添加库,package,install,export可以说把编译C/C++工程的方方面面都涉及了。但也正是因为它覆盖全面,所以它不可能方方面面都讲得很详细。
  在看的时候建议同时翻看“cmake-commands”、“cmake-properties”和“cmake-variables”的文档。因为教程里面只说了用什么命令,但是没有具体介绍这个命令里面每个参数的含义。另外也需要多查阅“Reference Manuals”里的其它文档。目前我觉得比较常用的命令有这些:

命令 作用
cmake_minimum_required 这个在写顶层CMakeLists的时候需要用到,指定cmake的最低需求版本
option 添加编译的选项,可以用变量控制生成不同需求的工程
project 创建工程,这个必须有,但我一开始经常忘
add_subdirectory 添加子目录,有顶层CMakeLists自然有次一层的CMakeLists,这个命令可以用来管理整个目录的所有源文件
add_executable/add_library 添加target,指定要编译的目标。编译可执行文件或者编译库。
file(GLOB …) 将目录下的符合给定glob表达式的文件名存到一个list里。还有GLOB_RECURSE也常用,可以递归搜索文件。这个在源文件或者头文件很多的情况下很有用
target_include_directories 目标的头文件路径
target_link_directories 目标的需要链接的文件所在的路径
install 指明需要安装的东西,在执行install的时候会将指定的文件放到给定的目录下面

  CMake的语法以及命令的参数官网的文档上都有,每次用的时候多查阅一下自然就记住了。在大概熟悉CMake之后可以VS Code里的CMake Tools插件用起来,这个插件省去了cmake-gui的操作,cmake的configure、generate、build、install包括test都有非常好的支持。相关的设置可以通过settings.json来完成,不好的地方就是它可以设置的变量没有cmake-gui那么可以直观地看到,对自定义的kit支持不是很好,但不管怎样它都已经很强大了。

CMake交叉编译

  在cmake-gui中选择generator,比如确定MinGW或者MSVC,那么CMake自然能够知道用什么编译指令来编译什么类型的源文件或者对目标文件进行链接。但是如果不是它默认支持的编译器,它怎么知道该怎样编译呢?比如TI的编译器,CMake能支持吗?

CMake支持TI的编译器吗?

  支持。下面是在编译成功后,Makefile依赖的cmake文件。其中的“TI.cmake”就给出了命令的格式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# The top level Makefile was generated from the following files:
set(CMAKE_MAKEFILE_DEPENDS
"CMakeCache.txt"
"C:/Program Files/CMake/share/cmake-3.22/Modules/CMakeCInformation.cmake"
"C:/Program Files/CMake/share/cmake-3.22/Modules/CMakeCXXInformation.cmake"
"C:/Program Files/CMake/share/cmake-3.22/Modules/CMakeCommonLanguageInclude.cmake"
"C:/Program Files/CMake/share/cmake-3.22/Modules/CMakeGenericSystem.cmake"
"C:/Program Files/CMake/share/cmake-3.22/Modules/CMakeInitializeConfigs.cmake"
"C:/Program Files/CMake/share/cmake-3.22/Modules/CMakeLanguageInformation.cmake"
"C:/Program Files/CMake/share/cmake-3.22/Modules/CMakeSystemSpecificInformation.cmake"
"C:/Program Files/CMake/share/cmake-3.22/Modules/CMakeSystemSpecificInitialize.cmake"
"C:/Program Files/CMake/share/cmake-3.22/Modules/Compiler/CMakeCommonCompilerMacros.cmake"
"C:/Program Files/CMake/share/cmake-3.22/Modules/Compiler/TI-C.cmake"
"C:/Program Files/CMake/share/cmake-3.22/Modules/Compiler/TI-CXX.cmake"
"C:/Program Files/CMake/share/cmake-3.22/Modules/Compiler/TI.cmake"
"C:/Program Files/CMake/share/cmake-3.22/Modules/Platform/Generic.cmake"
"D:/PrjCCS_CMake/Hello/CMakeLists.txt"
"CMakeFiles/3.22.1/CMakeCCompiler.cmake"
"CMakeFiles/3.22.1/CMakeCXXCompiler.cmake"
"CMakeFiles/3.22.1/CMakeSystem.cmake"
"D:/PrjCCS_CMake/toolchain.cmake"
)

  一开始我在网上找各种用CMake编译CCS工程的方法,但是都没有比较详细的说法,后来我就在CMake的安装目录下找。最后在Modules目录下找到了一个CMakeAddNewLanguage.txt的文件,后来又摸索了一下大概明白只需要设定编译器的名称,它就能知道需要用什么样的编译命令以及编译器的一些feature。比如我设置编译器的名称里包含cl6x,CMake就能知道这是TI的C6000系列的编译器,支持C89和C++98。

工具链设置

  官方文档中介绍,可以给CMake指定一个toolchain file,这个文件里的设置会被比较早地加载。
  交叉编译的关键是手动设置CMAKE_SYSTEM_NAME这个变量,这个变量表示目标设备的名称,这个变量也不是随便设置的,一般是Modules/Platform目录下的一些前缀,没有操作系统的裸核开发一般都设置成“Generic”。

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
26
27
28
29
30
31
32
# toolchain.cmake
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_VERSION 1.0)
set(CMAKE_SYSTEM_PROCESSOR tidsp)

set(C6000_CG_ROOT C:/ti/ccs620/ccsv6/tools/compiler/c6000_7.4.4)
set(CMAKE_C_COMPILER "${C6000_CG_ROOT}/bin/cl6x.exe")
set(CMAKE_CXX_COMPILER "${C6000_CG_ROOT}/bin/cl6x.exe")

set(CMAKE_FIND_ROOT_PATH ${C6000_CG_ROOT})
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)

include_directories("${C6000_CG_ROOT}/include")
link_directories("${C6000_CG_ROOT}/lib")

set(CMAKE_C_FLAGS " \
-mv6600 \
--abi=eabi \
--diag_wrap=off \
--diag_warning=225 \
--display_error_number")

set(CMAKE_CXX_FLAGS " \
-mv6600 \
--abi=eabi \
--diag_wrap=off \
--diag_warning=225 \
--display_error_number")

add_link_options("--rom_model")

  上面是我的toolchain的设置,结合TI的编译器的文档,参考CCS的编译和链接的命令,为编译C和C++文件分别添加的相应的flags。链接是在编译的基础上增加选项,就是链接的时候实际上也用到了前面的编译用的flag。

简单示例

  用最简单的Helloworld来演示,用一份源文件生成TI DSP的目标文件和Windows上的可执行文件。下面是我的源文件目录下的CMakeLists,用变量BUILD_CCS_PROJECT变量来控制生成什么类型的工程。

1
2
3
4
5
6
7
8
9
10
11
12
cmake_minimum_required(VERSION 3.22)

option(BUILD_CCS_PROJECT "build ccs project" ON)

project(hello)

if(BUILD_CCS_PROJECT)
add_executable(hello.out hello.cpp lnk.cmd)
set_property(SOURCE lnk.cmd PROPERTY EXTERNAL_OBJECT True)
else()
add_executable(hello hello.cpp)
endif()

cl6x编译

  首先是利用cl6x进行编译,这里值得注意的是,DSP工程里一般都有一个链接脚本文件“xxx.cmd”,在这里我们需要将它当作一个外部输入的object来参与编译,cl6x会自动分辨是object还是链接脚本。下图是用在CCS中打开Terminal,然后在另一个路径“Hello_CCS”下用gmake编译的结果。

  当时在toolchain设置文件里,在编译的flag里加了“-g”的选项,所以这个编译的结果能拿来在线仿真。下图是上板调试的结果。

MinGW编译

  然后是利用MinGW进行编译和运行,也得到了正确的结果。

EMCV(Embedded Computer Vision Library)

  本来准备做软件算法到DSP的移植,然后软件算法中用到了OpenCV3,我本来想,要是DSP也有类似的OpenCV的库就好了,然后就在网上找到了这个EMCV,本来以为这样子移植的工作能轻松很多,但是后来发现这个库是在OpenCV1.x基础上移植的,而OpenCV1和OpenCV3对于数据类型的定义就相差很大,所以我之前的想法是基本上用不上了,还是得自己去相应的数据类型。而且不光是这样,cl6x对C++的支持情况也有待确认。
  EMCV的代码是2013年Yu Shiqi在SourceForge上传的,源代码链接。我用7.4.4版本的cl6x编译器编译后修改了一些小问题。
  一个是下面的类型定义,CvFuncTable是一个用来存放函数指针的结构体,所以应该改成下面这样,而不是void类型的数据指针。这里改了之后,其它相关的地方用(void*)的强制类型转换也需要改为用(void(*)())进行强制函数指针类型转换。

1
2
3
4
5
6
7
8
9
// cxcore/cxmisc.h
...
typedef struct CvFuncTable
{
// void* fn_2d[CV_DEPTH_MAX];
void(*fn_2d[CV_DEPTH_MAX])();
}
CvFuncTable;
...

  另一个改的地方是下面这个EXIT代码无法运行到这里,可以删去。

1
2
3
4
5
6
7
8
// cxsumpixels.cpp
...
if( CV_IS_MATND(mat) )
{
CV_ERROR( CV_StsBadArg, "Only mat are supported here" );
// EXIT; can't reach
}
...

  修改后的以及用CMakeLists管理的EMCV源码已经上传至Gitee:链接