
OpenHarmony设备开发 轻量系统STM32F407芯片移植案例
介绍基于STM32F407IGT6
芯片在拓维信息Niobe407开发板上移植OpenHarmony LiteOS-M轻量系统,提供交通、工业领域开发板解决方案。移植架构采用Board
与SoC
分离方案,使用arm gcc
工具链Newlib C
库,实现了lwip
、littlefs
、hdf
等子系统及组件的适配,开发了配套应用示例代码,支持通过Kconfig图形化配置编译选项。
适配准备
- 下载stm32cubemx图形工具。
- 准备ubuntu20.04系统环境,安装arm-none-eabi-gcc交叉编译工具链。
生成可用工程
通过stm32cubemx工具生成STM32F407IGT6
芯片的Makefile工程,在此给出如下配置建议:
- 系统相关配置采用默认配置。
- 时钟配置时将SYSCLK选项配置为168MHz,发挥芯片最强性能。
- 配置USART1用作调试串口,用来打印适配过程中的调试信息。
- 配置stm32cubemx工程选项时,将Toolchain/IDE选项选为Makefile。
生成的工程目录如下:
验证生成的工程
将生成的工程拷贝至Ubuntu,进入工程目录下执行make命令编译,确定能够编译成功。
编译完成会生成一个.bin文件,为了确认该程序能在开发板中成功运行,需要main函数中的串口初始化之后,通过串口输出一段字符串,运行时若收到打印信息,则开发板启动成功。
适配printf输出到串口,只需要重写_write函数即可,参考如下:
重新编译代码,将其烧录至开发板中验证。
编译构建
目录规划
芯片适配目录规划为:
产品样例目录规划为:
获取OpenHarmony源码,根据上述目录规划,创建相应文件夹。
预编译适配
预编译适配内容就是围绕hb set
命令的适配,使工程能够通过该命令设置根目录、单板目录、产品目录、单板公司名等环境变量,为后续适配编译做准备。
具体的预编译适配步骤如下:
- 在
vendor/talkweb/niobe407
目录下新增config.json
文件,用于描述这个产品样例所使用的单板、内核等信息,描述信息可参考如下内容:
- 在
//device/board/talkweb/niobe407
目录下创建board
目录,在创建的目录下新增一个config.gni
文件,用于描述该产品的编译配置信息:
- 验证
hb set
配置是否正确,输入hb set
能够显示如下信息:
- 通过
hb env
可以查看选择出来的预编译环境变量:
- hb介绍
hb
是OpenHarmony为了方便开发者进行代码构建编译,提供的python脚本工具,其源码就在//build/lite
仓库目录下。在执行hb set
命令时,脚本会遍历//vendor/<product_company>/<product_name>
目录下的config.json
,给出可选产品编译选项。在config.json文件中,product_name
表示产品名,device_company
和board
用于关联出//device/board/<device_company>/<board>
目录,匹配该目录下的<any_dir_name>/config.gni
文件,其中<any_dir_name>
目录名可以是任意名称,但建议将其命名为适配内核名称(如:liteos_m、liteos_a、linux)。hb命令如果匹配到了多个config.gni
,会将其中的kernel_type
和kernel_version
字段与vendor/<device_company>
下config.json
文件中的字段进行匹配,从而确定参与编译的config.gni
文件。
至此,预编译适配完成,但工程还不能执行hb build
进行编译,还需要准备好后续的LiteOS-M
内核移植。
内核移植
内核移植需要完成LiteOS-M Kconfig
适配、gn
的编译构建和内核启动最小适配。
Kconfig文件适配
- 在
//vendor/talkweb/niobe407
目录下创建kernel_configs目录,并创建空文件,命名为debug.config。 - 打开
//kernel/liteos_m/Kconfig
文件,可以看到在该文件通过orsource命令导入了//device/board
和//device/soc
下多个Kconfig文件,后续需要创建并修改这些文件:
- 在
//device/board/talkweb
下参考如下目录结构创建相应的Kconfig文件:
- 修改
Kconfig
文件内容:
- 在
//device/board/talkweb/Kconfig.liteos_m.boards
文件中添加:
- 在
//device/board/talkweb/Kconfig.liteos_m.defconfig.boards
文件中添加:
- 在
//device/board/talkweb/Kconfig.liteos_m.defconfig.boards
文件中添加:
- 在
//device/board/talkweb/niobe407/Kconfig.liteos_m.board
文件中添加:
- 在
//device/board/talkweb/niobe407/Kconfig.liteos_m.defconfig.board
中添加:
5.在//device/soc/st
下参考如下目录结构创建相应的Kconfig文件,并将stm32cubemx
自动生成工程中的Drivers目录拷贝至stm32f4xx/sdk
目录下:
6.修改Kconfig文件内容:
- 在
//device/soc/st/Kconfig.liteos_m.defconfig
中添加:
- 在
//device/soc/st/Kconfig.liteos_m.series
中添加:
- 在
//device/soc/st/Kconfig.liteos_m.soc
中添加:
- 在
//device/soc/st/stm32f4xx/Kconfig.liteos_m.defconfig.series
中添加:
- 在
//device/soc/st/stm32f4xx/Kconfig.liteos_m.defconfig.stm32f4xx
中添加:
- 在
//device/soc/st/stm32f4xx/Kconfig.liteos_m.series
中添加:
- 在
//device/soc/st/stm32f4xx/Kconfig.liteos_m.soc
中添加:
7.在kernel/liteos_m
目录下执行make menuconfig
,使得能够对SoC Series
进行选择:
结果将自动保存在$(PRODUCT_PATH)/kernel_configs/debug.config
,下次执行make menuconfig
时会导出保存的结果。
BUILD.gn文件适配
为了快速熟悉gn的编译和适配,建议先阅读 LiteOS-M内核BUILD.gn编写指南。
(注意,BUILD.gn文件中不要出现tab字符,所有tab用空格代替)
- 在
kernel/liteos_m/BUILD.gn
中,可以看到,通过deps
指定了Board
和SoC
的编译入口:
2.在//device/board/talkweb/BUILD.gn
中,新增内容如下:
3.在niobe407目录下创建BUILD.gn,为了方便管理,将目录名作为模块名:
4.将stm32cubemx生成的示例工程Core目录下的文件、startup_stm32f407xx.s
启动文件和STM32F407IGTx_FLASH.ld
链接文件拷贝至//device/board/talkweb/niobe407/liteos_m/
目录下,并在该目录下创建BUILD.gn
,添加如下内容:
5.在make menuconfig中配置(Top) → Compat → Choose libc implementation
,选择newlibc
。
6.由于_write函数会与kernel的文件操作函数重名,会导致编译失败。后续会换一种方法来适配printf函数,此处我们先将main.c文件中对_write函数的重写删除,将printf函数改用如下方式进行串口打印测试。
7.同理//device/soc/st/BUILD.gn
也是一样,按照目录结构层层依赖包含,最终在//device/soc/st/stm32f4xx/sdk/BUILD.gn
中通过kernel_module
模板中指定需要参与编译的文件及编译参数,参考如下:
config.gni文件适配
在预编译阶段,在//device/board/talkweb/niobe407/liteos_m
目录下创建了一个config.gni文件,它其实就是gn脚本的头文件,可以理解为工程构建的全局配置文件。主要配置了CPU型号、交叉编译工具链及全局编译、链接参数等重要信息:
如上所示,比较难理解的就是board_opt_flags、board_cflags、board_asmflags等几个参数配置。可以参考如下描述,从stm32cubemx生成的工程中的Makefile
文件中提取出来:
内核子系统适配
在//vendor/talkweb/niobe407/config.json
文件中添加内核子系统及相关配置,如下所示:
target_config.h文件适配
在//kernel/liteos_m/kernel/include/los_config.h
文件中,有包含一个名为target_config.h的头文件,如果没有这个头文件,则会编译出错。
该头文件的作用主要是定义一些与soc芯片相关的宏定义,可以创建一个空头文件,再配合编译报错提示信息来确定需要定义哪些宏。经验证,Cortex-M4的核适配只需定义LOSCFG_BASE_CORE_TICK_RESPONSE_MAX
宏并包含stm32f4xx.h
头文件即可将kernel编译通过。
若前期不知如何配置,可以参考虚拟机qemu示例中//device/qemu/arm_mps2_an386/liteos_m/board/target_config.h
的配置。
其中宏定义LOSCFG_BASE_CORE_TICK_RESPONSE_MAX
是直接参考的//device/qemu/arm_mps2_an386/liteos_m/board/target_config.h
文件中的配置,//device/qemu/arm_mps2_an386
是cortex-m4
的虚拟机工程,后续适配可以直接参考,在此不做深入讲解。
内核启动适配
至此,已经可以成功将kernel子系统编译通过,并且在out目录下生成OHOS_Image.bin文件。将生成的OHOS_Image.bin文件烧录至开发板,验证板子能否正常启动运行,如果能成功打印出main函数中串口输出的正确的打印信息,则可以开始进行内核启动适配。
- 为liteos_m分配内存,适配内存分配函数
在文件//kernel/liteos_m/kernel/src/mm/los_memory.c
中,OsMemSystemInit
函数通过LOS_MemInit进行了内存初始化。可以看到几个比较关键的宏需要我们指定,我们将其添加到target_config.h
中:
其中,__los_heap_addr_start__
与__los_heap_addr_end__
变量在STM32F407IGTx_FLASH.ld
链接文件中被定义, 将_user_heap_stack花括号内内容修改为:
除此之外,我们还需要适配内存分配函数,由于内核中已经对_malloc_r等内存分配函数进行了实现,在此我们采用包装函数的方式来适配,用内核中的内存分配函数替换标准库中的内存分配函数即可,在//device/board/talkweb/niobe407/liteos_m/config.gni
中board_ld_flags链接参数变量修改为:
2.适配printf打印
为了方便后续调试,第一步需要先适配printf函数。而printf的函数适配可大可小,在此只做简单适配,具体实现可以参考其它各开发板源码。
在main.c同级目录下创建dprintf.c文件,文件内容如下:
将dprintf.c文件加入BUILD.gn编译脚本,参与编译。
在串口初始化之后使用printf函数打印,测试是否适配成功。
3.调用LOS_KernelInit初始化内核,进入任务调度。
在main函数中串口初始化之后,调用LOS_KernelInit
进行初始化,创建任务示例,进入任务调度。
其中TaskSample()
函数内容如下:
适配完内核启动后,可以通过调试串口看到如下打印信息:
后续还需要对整个基础内核进行详细适配验证。
内核基础功能适配
内核基础功能适配项包括:中断管理、任务管理、内存管理、内核通信机制、时间管理、软件定时器,可以参考对应链接中的编程实例进行内核基础功能验证。在验证的过程中发现问题,针对相应问题进行具体的适配。
从上一节中打印信息输出时间间隔可以看出,LOS_TaskDelay
函数的延时时间不准确,我们可以在target_config.h
中定义如下宏进行内核时钟适配:
其它内核基础功能的适配方法大多也是围绕于target_config.h
中的宏定义,需要大家配合//kernel/liteos_m
下源码,自行尝试摸索,在此不做进一步讲解。
littlefs文件系统移植适配
Niobe407
开发板外挂了16MB的SPI-FLASH,Niobe407基于该Flash进行了littlefs适配。
内核已经对littlefs进行了适配,我们只需要开启Kconfig中的配置,然后适配Littlefs如下接口:
W25x_BufferRead
等函数是spi-flash读写操作的接口,不同型号的spi-flash其实现也不同,Niobe407的SPI-Flash操作具体实现可参考//device/board/talkweb/niobe407/liteos_m/drivers/spi_flash/src/w25qxx.c
由于SPI已经hdf化了,而littlefs依赖于spi驱动,为了方便对文件系统进行配置,可以将littlefs的配置加入至.hcs文件中,具体参考://device/board/talkweb/niobe407/liteos_m/hdf_config/hdf_littlefs.hcs
文件
板级驱动移植
驱动适配相关文件放置在//drivers/adapter/platform
中,对应有gpio
,i2c
,pwm
,spi
,uart
,watchdog
,都是通过HDF
机制加载,本章节以pwm
为例进行说明。
PWM驱动适配
在HDF框架中,PWM的接口适配模式采用独立服务模式,在这种模式下,每一个设备对象会独立发布一个设备服务来处理外部访问,设备管理器收到API的访问请求之后,通过提取该请求的参数,达到调用实际设备对象的相应内部方法的目的。独立服务模式可以直接借助HDF DeviceManager的服务管理能力,但需要为每个设备单独配置设备节点。
- 接口说明
- PWM HDF HCS配置文件解析
device_info.hcs
文件位于//device/board/talkweb/niobe407/liteos_m/hdf_config/device_info.hcs
,以下示例为使用TIM2、TIM3和TIM7定时器输出PWM信号:
hdf.hcs
文件位于//device/board/talkweb/niobe407/liteos_m/hdf_config/hdf.hcs
,在此文件中配置TIM定时器具体信息:
hdf pwm
适配代码请参考://drivers/adapter/platform/pwm/pwm_stm32f4xx.c
hdf pwm
使用示例可请参考://device/board/talkweb/niobe407/applications/206_hdf_pwm
子系统适配
OpenHarmony
子系统适配一般包含两部分:
- 在
config.json
中增加对应子系统和部件,这样编译系统会将该部件纳入编译目标中。 - 针对该部件的
HAL
层接口进行硬件适配,或者可选的软件功能适配。
LWIP部件适配
LiteOS-M kernel
通过Kconfig配置可以使lwip参与编译,并可以在kernel
组件中指定lwip
编译适配的目录。如下:
在指定的编译适配目录中,通过#include_next "lwip/lwipopts.h"
的方式入侵修改lwip三方库中头文件配置,关于有线以太网LWIP适配部分,后续会补充详细适配步骤,在此先不做深入讲解。
启动恢复子系统适配
启动恢复子系统适配bootstrap_lite
和syspara_lite
两个组件。请在//vendor/talkweb/niobe407/config.json
中新增对应的配置选项。
适配bootstrap_lite
部件时,需要在链接文件//device/board/talkweb/niobe407/liteos_m/STM32F407IGTx_FLASH.ld
中手动新增如下段:
需要新增上述段是因为bootstrap_init
提供的对外接口,见//utils/native/lite/include/ohos_init.h
文件,采用的是灌段的形式,最终会保存到上述链接段中。主要的服务自动初始化宏如下表格所示:
接口名 | 描述 |
SYS_SERVICE_INIT(func) | 标识核心系统服务的初始化启动入口 |
SYS_FEATURE_INIT(func) | 标识核心系统功能的初始化启动入口 |
APP_SERVICE_INIT(func) | 标识应用层服务的初始化启动入口 |
APP_FEATURE_INIT(func) | 标识应用层功能的初始化启动入口 |
通过上面加载的组件编译出来的lib文件需要手动加入强制链接。
如在 //vendor/talkweb/niobe407/config.json
中配置了bootstrap_lite
部件
bootstrap_lite
部件会编译//base/startup/bootstrap_lite/services/source/bootstrap_service.c
,该文件中,通过SYS_SERVICE_INIT
将Init
函数符号灌段到__zinitcall_sys_service_start
和__zinitcall_sys_service_end
中,由于Init
函数是没有显式调用它,所以需要将它强制链接到最终的镜像。如下:
在//base/startup/bootstrap_lite/services/source/BUILD.gn
文件中,描述了在//out/niobe407/niobe407/libs
生成 libbootstrap.a
,如下:
适配syspara_lite
部件时,系统参数会最终写到文件中进行持久化保存。在轻量系统中,文件操作相关接口有POSIX
接口与HalFiles
接口这两套实现。
因为对接内核的文件系统,采用POSIX
相关的接口,所以features
字段中需要增加enable_ohos_startup_syspara_lite_use_posix_file_api = true
。
如果对接HalFiles
相关的接口实现的,则无须修改。
DFX子系统适配
进行DFX
子系统适配需要添加hilog_lite
和hievent_lite
部件,直接在config.json
文件配置即可。
配置完成之后,需要注册日志输出实现函数,并加入编译。
系统服务管理子系统适配
进行系统服务管理子系统适配需要添加samgr_lite
部件,直接在config.json
配置即可。
在轻量系统中,samgr_lite
配置的共享任务栈大小默认为2048
。在适配时可以在features中,通过config_ohos_systemabilitymgr_samgr_lite_shared_task_size
重新设置共享任务栈大小。
安全子系统适配
进行安全子系统适配需要添加huks
组件,直接在config.json
配置即可。
huks
部件适配时,huks_key_store_path
配置选项用于指定存放秘钥路径,huks_config_file
为配置头文件名称。
公共基础库子系统适配
公共基础库子系统适配添加了kv_store
、file
、os_dump
组件,直接在config.json
配置即可。
与适配syspara_lite
部件类似,适配kv_store
部件时,键值对会写到文件中。在轻量系统中,文件操作相关接口有POSIX
接口与HalFiles
接口这两套实现。因为对接内核的文件系统,采用POSIX
相关的接口,所以features
需要增加enable_ohos_utils_native_lite_kv_store_use_posix_kv_api = true
。如果对接HalFiles
相关的接口实现的,则无须修改。
HDF子系统适配
与启动恢复子系统适配类似,我们需要在链接文件//device/board/talkweb/niobe407/liteos_m/STM32F407IGTx_FLASH.ld
中手动新增如下段:
然后,在kernel初始化完成后调用DeviceManagerStart函数,执行完成后,才能调用hdf接口控制外设。
devmgr_service_start.h
头文件所在路径为: //drivers/framework/core/common/include/manager
,为保证编译时能找到该头文件,需要将其加入到include_dirs中:
XTS兼容性测评子系统适配
产品兼容性规范
产品兼容性规范文档请参考产品兼容性SIG介绍。
添加XTS子系统
XTS
测试参考资料见xts参考资料,进行XTS
子系统适配需要添加xts_acts
与xts_tools
组件,直接在config.json
配置即可,配置如下:
我们可以在xts_acts组件的features数组中指定如下属性:
-
config_ohos_xts_acts_utils_lite_kv_store_data_path
配置挂载文件系统根目录的名字。 -
enable_ohos_test_xts_acts_use_thirdparty_lwip
表示如果使用thirdparty/lwip
目录下的源码编译,则设置为true
,否则设置为false
。
编译XTS
在配置config.json后,使用hb build
是不会去编译xts的,只有在debug版本编译时才会参与编译,并且需要我们强制链接需要进行测试的套件静态库。
在我们//device/board/talkweb/liteos_m
下包含kernel_module
的BUILD.gn 脚本中添加如下内容:
由于Niobe407开发板内存有限,xts测试时需要分套件测试。执行如下编译命令,即可生成包含xts测试的固件。
此外,我们还需要修改//vendor/talkweb/niobe407/hals/utils/sys_param/hal_sys_param.c
文件,将这些字符串定义正确。
验证XTS
编译完成后,将固件烧录至开发板,xts全部跑完会有显示xx Tests xx Failures xx Ignored
等信息,以下以公共基础库测试为例:
