阿里云IOT-C-SDK系列(1)概述:移植流程、程序框架、代码目录
阿里云IOT-C-SDK系列(2)快速体验:移植+示例C代码
在系列(2)中详细的罗列了如何使用SDK自带的编译系统进行移植,正如前面所述,使用SDK自带的编译系统看起来简单,但是由于SDK对很多的细节进行了封装,所以对于我们深入的理解SDK反倒是不好的,所以阿里的技术支持更加推荐用户能够使用“代码抽取”功能,将抽取出的代码放到我们自己的工程项目中,我们根据自己的软硬件环境,我们自己来写Makefile编译SDK。
1、“代码抽取” 移植方式的流程及注意事项
正如前两篇文章所述,执行代码抽取 脚本 后,会将我们需要的SDK功能对应的源码抽取出来,抽取目录里一般会有个Makefile,这个Makefile是一个比较贴心的东西,运行该Makefile会生成2个库文件:libiot_sdk.a 和 libiot_hal.a,其中:
libiot_sdk.a : SDK的核心库,是SDK的软件功能实现的库。
libiot_hal.a : SDK提供的HAL层的库,这个是 跟我们硬件环境相关的,换言之,是对应那个wrappers.c中的各种函数的编译结果。
但是要特别注意:我们的移植工作的重点是 要 实现 wrappers.c中的所有 HAL_xxx接口函数,而抽取出的源码,默认情况下,wrappers.c中的HAL_XXX函数都是空的,只有框架,所以需要我们自己去填充。
另外,这个自带的Makefile的使用也并不是一定最好的,因为如果我们不去实现HAL_xxx接口函数,即便我们运行Makefile,那么 生成的库也是没法使用的。所以最好是自己去写Makefile,这样虽然开始难一些,但是一旦调试完成了,不管是对于我们写Makefile的水平,还是对SDK的理解,能更加深入。所以本文采用 代码抽取 移植方式+ 自己编写Makefile来实现。流程如下:
(1)运行 make menuconfig(Windows下运行 config.bat),进行功能选择。即选择SDK中我们需要的功能,相当于裁剪SDK,该步骤会生成 make.setting 文件,里面罗列了我们选择的功能项,用于步骤(2)的输入。
(2)运行 extract.sh (Windows 下运行 extract.bat),进行代码抽取。
(3) 步骤2的抽取结果是在当前目录下创建 output 目录,里面包含2个子目录 eng 、examples,和1个Makefile,其中:
eng:抽取的功能源码。 examples:SDK自带的示例代码, Makefile:方便生成 库文件的 Makefile。
(4)将步骤3中生成的 eng 文件夹 放入到我们的工程目录下,这里examples只是编程示范,是不需要放入我们的工程文件的,Makefile文件更不需要。
(5)实现我们eng/wrappers/wrapper.c中的 所有 HAL_XXX接口函数。
(6)如果我们使用的是Keil或IAR, 那就简单了,不用我们自己来写Makefile了,用IDE自带的编译系统就好了,而Linux下是需要自己来写Makefile的,这里对于那些高手来讲就太简单了,不过我连个熟练手都算不上,只能算是入门级。
(7)编写我们自己的业务逻辑代码,关于如何使用SDK,当然要借鉴output/examples里的 案例了。
2、wrapper.c 的实现注意事项
wrapper.c是SDK的 代码抽取 脚本的最终输出文件,需要我们取提供文件中 罗列的各种 HAL_XXX函数,因为默认各个函数为空。这里需要特别注意的几点:
(1) 不能认为只要实现HAL_XXX函数就万事大吉了,我们在实现HAL_XXX接口函数的时候,一定记得要 添加 相关的头文件,否则我们在编译的时候,会出现各种各样的错误提示的。
(2)wrapper.c中只罗列了SDK运行所必须的 的 HAL_XXX接口 函数,所以我们才会发现,wrapper.c中的 HAL_XXX函数并 不多,这绝不代表 只有这些 HAL 函数,如果我们使用xxx_api.h 中的 其他HAL_XXX函数,我们是需要在wrapper.c中实现它的, 或者在其他地方, 总之只要是HAL_XXX函数,不管是在wrapper.c中还是其他地方出现的,都是 需要我们去实现的。举例:我们在linkkit_example_gateway.c 示例代码中,发现了 HAL_ThreadCreate 函数,很明显这是线程创建函数,我们搜寻源码,发现这个 函数 定义在 源码 wrapper/os/ubuntu/HAL_OS_linux.c中,这本身就是 SDK 给我们写好的 在ubuntu下的 接口函数,如果我们的编程环境是 linux,我们想用抽取 代码结果 wapper.c 之外的 HAL_XXX函数,记住, 一定要先实现他。
3、编译抽取出的功能源码的 Makefile 示范
虽然抽取的功能源码并不是特别多,但是如果逐个文件去编译,然后连接,估计也得抓狂,这也不是Makefile的 特色,Makefile强大的功能,几乎只有我想不到,没有它干不成的,尤其是 传说中的那个 正规表达式,简直了,对于之前使用IDE编程的人来说,说震撼一点都不夸张。
这里的难点是,如何一次性的对 抽取代码进行编译,参考抽取代码中 Makefile的方式,先上代码,然后再分析,核心Makefile代码如下:
CROSS = arm-linux-gnueabihf-
CC = $(CROSS)gcc
STRIP = $(CROSS)strip
CFLAGS = -g -O2 -Wall
# Aliyun SDK compile
# source dir
SRCDIR := eng
# all the *.c except wrappers/
IOT_SDK_SOURCE_FILES_C := $(shell find $(SRCDIR) -name "*.c" -not -path "*wrappers*")
# the dir wrappers/ *.c
IOT_SDK_WRAPPER_IMPL_S := $(shell find $(SRCDIR) -name "*.c" -path "*wrappers*")
# include all the dir path
IOT_SDK_HDRDIR := $(shell find $(SRCDIR) -type d)
# add the -I to the IOT_SDK_HDRDIR
IOT_SDK_HDRDIR := $(addprefix -I,$(IOT_SDK_HDRDIR))
#include
INC = -I./bsp -I./bsp/wraperror \
-I./bsp/wrappthread \
-I./bsp/event $(IOT_SDK_HDRDIR)
#lib
LIBS = -lpthread -lrt
#src
SRC = main.c bsp/wraperror/wraperror.c \
bsp/wrappthread/wrappthread.c \
bsp/event/portevent.c \
$(IOT_SDK_SOURCE_FILES_C) \
$(IOT_SDK_WRAPPER_IMPL_S)
#target
TARGET = test
#objs
OBJS = $(SRC:.c=.o)
$(TARGET):$(OBJS)
$(CC) -o $@ $^ $(LIBS)
.PHONY: clean
clean:
rm -f $(OBJS)
install: $(TARGET) clean
@echo start compile...
@echo end.
%.o:%.c
$(CC) $(CFLAGS) $(INC) $(LIBS) -o $@ -c $<
上面的大部分代码,我们分别用汉语来解释:
# 定义交叉编译器
CROSS = arm-linux-gnueabihf-
CC = $(CROSS)gcc
# 定义交叉编译器 编译选项
STRIP = $(CROSS)strip
CFLAGS = -g -O2 -Wall
# 定义变量 SRCDIR,也就是eng目录
SRCDIR := eng
# 这里使用了 Makefile中嵌套 shell命令来实现 一条命令包含所有的 *.c文件
# 相当于运行了 find eng -name "*.c" -not -path "*wrappers*" ,这条命令的结果就是 查找
# eng目录(包含子目录)下所有的名字为 *.c 的文件,-not -path顾名思义,不包含 wrappers
# 这样做是为了 让我们一步步的编译的,比如我们可以先不去实现 wrapper.c 只编译其他核心代码
IOT_SDK_SOURCE_FILES_C := $(shell find $(SRCDIR) -name "*.c" -not -path "*wrappers*")
# 套路跟上面一样,就是获取 wrappers 目录下(包含子目录)的所有 *.c 文件
IOT_SDK_WRAPPER_IMPL_S := $(shell find $(SRCDIR) -name "*.c" -path "*wrappers*")
#通过上面两条命令一搞,这两个变量就代表了 所有的 *.c 文件,太牛掰了。
# include all the dir path
# 获取eng 目录下所有的 目录,这是为了包含那些头文件的。
IOT_SDK_HDRDIR := $(shell find $(SRCDIR) -type d)
# add the -I to the IOT_SDK_HDRDIR
# addprefix 是添加 -I 到 后面的字符串,这样就能 符合make中 包含 路径的语法了。
IOT_SDK_HDRDIR := $(addprefix -I,$(IOT_SDK_HDRDIR))
#include
INC = -I./bsp -I./bsp/wraperror \
-I./bsp/wrappthread \
-I./bsp/event $(IOT_SDK_HDRDIR)
#lib
# 编译 SDK,尤其是 linux环境下,需要加上 -lrt,因为HAL_XXX的 时间接口上使用了 rt库,
LIBS = -lpthread -lrt
#src
SRC = main.c bsp/wraperror/wraperror.c \
bsp/wrappthread/wrappthread.c \
bsp/event/portevent.c \
$(IOT_SDK_SOURCE_FILES_C) \
$(IOT_SDK_WRAPPER_IMPL_S)
#target
TARGET = test
#objs
#定义所有的 的目标 obj,这条命令的意思是 所有的 *.c 生成 对应的 *.o 文件
OBJS = $(SRC:.c=.o)
$(TARGET):$(OBJS)
$(CC) -o $@ $^ $(LIBS)
.PHONY: clean
clean:
rm -f $(OBJS)
install: $(TARGET) clean
@echo start compile...
@echo end.
#所有的 .o 文件生成规则
%.o:%.c
$(CC) $(CFLAGS) $(INC) $(LIBS) -o $@ -c $<