鸿蒙的驱动子系统-1-轻量系统驱动开发 原创 精华
鸿蒙的驱动子系统-1-轻量系统驱动开发
liangkz 2021.08.05
注:本系列文章基于LTS分支代码(本地代码根目录B_LTS/),对鸿蒙系统的设备驱动开发进行学习、分析和总结。
查看 //drivers/adapter/khdf/,这个目录下有一个liteos_m子目录,进入子目录查看readme文档:
该仓主要存放OpenHarmony驱动子系统适配liteos_m内核的代码和编译脚本,在liteos_m内核中部署OpenHarmony驱动框架。
但实际上,适配liteos_m内核的wifiiot项目,并没有使用和编译该仓库,验证方法也很简单,到这个目录下的代码或者编译脚本上,随便制造一些语法错误,再去编译wifiiot_hispark_pegasus项目,期待编译错误的出现,但实际上编译是可以正常通过的,说明并没有编译这个仓库。
打开//vendor/hisilicon/hispark_pegasus/config.json文件,这是Hi3861开发板平台的产品配置表,仔细查看一下子系统/组件列表信息,可以看到,没有哪个组件涉及到上面的khdf/liteos_m/组件的。实际上Hi3861开发平台上,与驱动开发相关的子系统/组件,包括了:
{
"subsystem": "applications",
"components": [
{ "component": "wifi_iot_sample_app", "features":[] }
]
},
{
"subsystem": "iot_hardware",
"components": [
{ "component": "iot_controller", "features":[] }
]
},
{
"subsystem": "iot",
"components": [
{ "component": "iot_link", "features":[] }
]
},
{
"subsystem": "vendor",
"components": [
{ "component": "hi3861_sdk", "target": "//device/hisilicon/hispark_pegasus/sdk_liteos:wifiiot_sdk", "features":[] }
]
},
四者的关系可分为三层:applications在上层,iot_hardware和iot在中间层,vendor的wifiiot_sdk为底层。底层是liteos内核给中间层提供基础支持,上层使用中间层提供的接口实现业务逻辑,中间层对上层提供接口,实现应用层与系统内核的隔离。
所以,鸿蒙的轻量系统,看上去并没有使用鸿蒙的HDF驱动开发框架,因为轻量系统实在是太轻量了,没必要用那么复杂的东西。
从上面的分析来看,鸿蒙的轻量系统的设备驱动开发,有两条开发路径可以走:
- 路径一:applications->iot_hardware->wifiiot_sdk
这条路径是最常规的,社区里现成的例子非常多,基本路子可以认为:
【4-1】拿到开发板和外设,研究相关的原理图,确认相关引脚的连线,找出需要控制的接口。
如WLAN模组的LED1灯(GPIO9)/USER按键(GPIO5);
又如OLED显示屏的I2C控制引脚:SDA/SCL,分别是GPIO13/GPIO14复用为 I2C0 这一组的两个信号。
【4-2】去 //device/hisilicon/hispark_pegasus/sdk_liteos/build/config/usr_config.mk 找到并打开/关闭相关的宏,让底层提供相对应的引脚功能。
如默认情况下GPIO5是作为UART1的Rx信号线引脚来使用的,要将GPIO5用到USER按键上,则需要把“CONFIG_UART1_SUPPORT=y”注释掉,修改成“# CONFIG_UART1_SUPPORT is not set”。
又如要写OLED显示屏的驱动程序,默认情况下“# CONFIG_I2C_SUPPORT is not set”,需要将其配置为“CONFIG_I2C_SUPPORT=y”,这样才会打开I2C功能的支持。
【4-3】打开/关闭上述配置的宏之后,还需要到 app_io_init.c 文件的 app_io_init()函数内,对相关的引脚做一些初始配置。不过这一步并非是必须的,也可以到具体使用这些引脚的地方去做初始化。可以根据实际需要进行处理。
如把“CONFIG_UART1_SUPPORT=y”注释掉之后,
#ifdef CONFIG_UART1_SUPPORT
/* uart1 AT命令串口 */
hi_io_set_func(HI_IO_NAME_GPIO_5, HI_IO_FUNC_GPIO_5_UART1_RXD); /* uart1 rx */
hi_io_set_func(HI_IO_NAME_GPIO_6, HI_IO_FUNC_GPIO_6_UART1_TXD); /* uart1 tx */
#endif
上面这段代码就不再编译,GPIO5处于未配置的默认状态,可以到使用GPIO5的地方通过中间层iot_hardware提供的接口去做初始化,下面的【4-4】有介绍。
而如配置“CONFIG_I2C_SUPPORT=y”之后,最好就是在这里增加代码,让GPIO13/14在系统启动阶段就默认配置为I2C0的两个信号引脚:
#ifdef CONFIG_I2C_SUPPORT
/* I2C0 */
hi_io_set_func(HI_IO_NAME_GPIO_13, HI_IO_FUNC_GPIO_13_I2C0_SDA);
hi_io_set_func(HI_IO_NAME_GPIO_14, HI_IO_FUNC_GPIO_14_I2C0_SCL);
#endif
因为在 app_main.c 的 peripheral_init() 内的app_io_init()之后,有一个初始化配置I2C0的动作:
#ifdef CONFIG_I2C_SUPPORT
ret = hi_i2c_deinit(HI_I2C_IDX_0); /* if wake_up from deep sleep, should deinit first */
ret |= hi_i2c_init(HI_I2C_IDX_0, 100000); /* baudrate: 100000 */
if (ret != HI_ERR_SUCCESS) {
err_info |= PERIPHERAL_INIT_ERR_I2C;
}
#endif
【4-4】接下来就是到//applications/sample/wifi-iot/app/目录下,按一个应用的开发流程去创建应用项目,调用中间层iot_hardware提供的接口去实现业务逻辑,完成和完善相关的硬件驱动功能。
如USER按键的功能,可以在这里做初始化为:
static void UsrButtonInit(void)
{
IoTGpioInit(IOT_GPIO_5);
IoTGpioSetFunc(IOT_GPIO_5, 0); //HI_IO_FUNC_GPIO_5_GPIO
IoTGpioSetDir(IOT_GPIO_5, IOT_GPIO_DIR_IN);
IoTGpioSetPull(IOT_GPIO_5, IOT_IO_PULL_UP);
IoTGpioRegisterIsrFunc(IOT_GPIO_5,
IOT_INT_TYPE_EDGE,
IOT_GPIO_EDGE_FALL_LEVEL_LOW,
UsrButtonPressed,
NULL);
}
而对于I2C0,如果已经在【4-3】做了配置和初始化,那这里就不再需要做初始化了,除非为了修改默认的配置参数,如:
#define IOT_I2C0_IDX (0)
#define IOT_I2C0_BAUD (400000) //modify to 400K,peripheral_init() default to 100K
void I2C_Init(void)
{
#if 0 //如果在上面的 app_io_init() 内已经做了 I2C0 两个引脚的配置工作,这里就不需要再做配置了。
IoTGpioInit(IOT_GPIO_13);
IoTGpioInit(IOT_GPIO_14);
IoTGpioSetFunc(IOT_GPIO_13, 6); //HI_IO_FUNC_GPIO_13_I2C0_SDA
IoTGpioSetFunc(IOT_GPIO_14, 6); //HI_IO_FUNC_GPIO_14_I2C0_SCL
#endif
//要不是为了修改baudrate参数,这里连 IoTI2cInit 都不需要了。
if(0 != IoTI2cInit(IOT_I2C0_IDX, IOT_I2C0_BAUD))
{
printf("I2C_Init: IoTI2cInit(I2C0, 400K) NG\n");
}
}
中间层iot_hardware提供的接口都在 //base/iot_hardware/peripheral/interfaces/kits/ 目录下的头文件有声明了,如果要用到一些没有声明和实现的接口,就需要开发者根据底层wifiiot_sdk提供的接口资源,自己来声明和实现了,如前文《OpenHarmony 2.0 Canary 的 IoT硬件子系统》提到的例子。
上面这条路径,适合于完全是开发者自己对所有代码都有控制权的情况。
但有很多时候,我们在实际项目开发中,使用的外围设备,芯片厂商出于对核心功能的保护,并不会提供完整的代码给我们,对于部分核心功能,只提供编译好的库文件和相关的接口声明,需要开发者自己调用库文件的提供的接口或者在平台上实现库文件预定义的接口,以此来达到对核心代码的保密。
这就需要通过第二条路径来实现驱动的开发了。
- applications->iot->wifiiot_sdk
这第二条路径就是官方给出的将三方SDK集成到鸿蒙系统的方法,详细情况见“设备开发->WLAN连接类产品->集成三方SDK”官方文档的说明。
下面以我做的OLED显示屏的驱动为例做一下简单的开发步骤介绍。
开发者仍然需要按照上面的【4-1】【4-2】【4-3】的步骤进行操作和配置,确保平台端的硬件配置正确。
【5-4】我已经以 //domains/iot/link/libbuild/ 为范本,加入OLED的相关驱动代码,编译出了一个 libdemosdk.a 库文件(见附件),权且当作OLED设备厂商提供的不对外开放的核心代码库。
这个库对外提供了一个调用接口:int DemoSdkEntry(void);
声明在://domains/iot/link/libbuild/demosdk.h 开发者可以把这个头文件部署到应用所在的代码目录中,也可以先保持目前默认的目录不动,//applications/sample/wifi-iot/app/demolink/BUILD.gn 文件中的 include_dirs 就包含着 "//domains/iot/link/libbuild" 目录,同目录下的helloworld.c中有#include "demosdk.h" ,即可调用DemoSdkEntry()。
这个库对外调用了需要平台实现的两个接口:
//default to init I2C0, and write to device by I2C0
unsigned int I2C_Init(void);
unsigned int I2C_Write(unsigned short deviceAddr, const unsigned char *data, unsigned int len);
一般来说三方提供库文件时,也会提供头文件的上述函数声明,要求开发者在移植代码/库文件时实现上述函数,我这里为了方便,一并将这两个函数的声明和实现放在了//domains/iot/link/demolink/demosdk_adapter.h+.c 中,类似demo中的 DemoSdkCreateTask() 和DemoSdkSleepMs()进行实现【见附件demolink.rar】。
【5-5】
下载附件并解压,将 libdemosdk.a 拷贝到 //device/hisilicon/hispark_pegasus/sdk_liteos/3rd_sdk/demolink/libs/ 目录下;
再将附件的 demolink.rar 代码解压并替换掉(替换前注意先备份代码) //domains/iot/link/demolink/ 代码【你自己实现也可以】,
修改 //domains/iot/link/BUILD.gn,增加:
lite_component("link") {
features = [
"demolink:demolinkadapter"
]
}
再去 //applications/sample/wifi-iot/app/BUILD.gn 修改增加:
lite_component("app") {
features = [
"demolink:example_demolink",
]
}
编译wifiiot后烧录到平台,就可以看到OLED点亮并显示相关内容了。
helloworld.c 通过调用 DemoSdkEntry() 进入三方SDK libdemosdk.a 跑相关的OLED驱动流程,OLED驱动通过调用 I2C_Init/I2C_Write 来写寄存器,实现硬件的驱动,具体写了些什么内容和如何驱动的,就是三方SDK保密的内容了。
三方SDK还可以提供更多的接口,以实现更复杂的功能,比如可以读取或修改OLED的显示状态、可以改变OLED显示的内容等等,这是后话了。
以上就是轻量系统上开发驱动程序的最基本的两个办法,实际开发中,遇到问题、解决问题,方法肯定不止上面两种,可以根据实际情况来选择最合适的方法来实现。
楼主好久不见,甚是想念。
^_^
好文支持
新专题!