基于HDF的LED驱动程序开发(2)
引言
本文以小熊派BearPi-HM_Micro_small开发板上的一个LED灯为例,介绍如何基于HDF框架开发一个外设的驱动程序。
在阅读本文之前,建议大家先阅读:《OpenHarmony驱动子系统概述》,对HDF框架有一个基本的了解。
另外,在编写LED灯的驱动程序时,我们会用到很多由HDF框架提供的API。为了便于查阅这些API的用法,建议大家在阅读本文的同时,打开文章《HDF驱动框架的API》(1)、(2)、(3)。在这几篇文章中汇集了本文所有用到的API。当然,你也可以直接去查阅这些API的源码和官方说明文档。
基于HDF框架进行设备驱动开发主要包括两部分工作:驱动配置 和 驱动实现 。
(1)驱动配置 :主要就是按照HDF框架所定义的设备驱动模型,以某种方式将设备驱动的配置信息(包括:所属设备集合及其属性、所属设备、设备节点及其属性)描述出来,提供给HDF驱动框架使用。只有这样,HDF驱动框架才能对设备驱动进行管理、加载和部署,应用程序才能通过驱动框架找到并使用设备驱动。
(2)驱动实现 :主要就是编写驱动的业务代码,实现驱动的具体功能(注:驱动的功能大多都是以驱动程序对外提供 服务 的形式呈现出来的)。
上一篇: 基于HDF的LED驱动程序开发(1)
二、驱动实现
驱动实现包括编写设备驱动的 业务代码 和 编译脚本 两个部分。
2.1 业务代码
驱动的业务代码包括三部分:驱动服务程序 、驱动入口函数 、注册驱动 。
新建一个文件:device/st/drivers/led/led.c
,用于存放LED的驱动程序。
2.1.1 驱动服务程序
驱动的功能大多都是以对外提供 服务 的形式呈现出来的。驱动服务程序主要由两个部分组成:驱动服务入口结构体 、驱动服务函数 。
1、驱动服务入口结构体
HDF提供了一个基本的驱动服务入口结构体类型IDeviceIoService
,开发者可以用它直接定义一个IDeviceIoService
类型的结构体作为驱动服务的入口。
当然,开发者也可以先自定义一个驱动服务入口结构体类型,然后再创建一个这种自定义的结构体作为驱动服务的入口,但是开发者自定义的驱动服务入口结构体类型中的第一个成员必须是IDeviceIoService
类型结构体。所以,无论是直接使用结构体类型IDeviceIoService
,还是自定义驱动服务入口结构体类型,驱动服务的入口都是IDeviceIoService
类型的结构体。
以下是在LED驱动程序中自定义的一个驱动服务入口结构体类型:
//自定义驱动服务入口结构体
struct LedDriverService {struct IDeviceIoService ioService; // 首个成员必须是IDeviceIoService结构体int32_t (*ServiceA)(void); // 驱动的服务函数int32_t (*ServiceB)(uint32_t inputCode); // 驱动的服务函数,可以添加多个驱动服务函数
};
可以看到:在HDF提供的结构体类型IDeviceIoService
中,只有一个驱动服务函数(Dispatch函数)入口/指针,而且这个驱动服务函数的参数和调用机制都是HDF已经规定好的,开发者仅仅是在这个函数中实现驱动服务;在自定义驱动服务入口结构体中,除了可以使用IDeviceIoService
结构体中的Dispatch函数以外,还可以再添加多个驱动服务函数指针,这些指针指向的驱动服务函数完全是由驱动开发者自己定义和实现的。
2、驱动服务函数
由上可知,驱动服务函数可以分成两种:Dispatch函数 、非Dispatch函数 。
(1)Dispatch函数
Dispatch函数就是HDF定义的结构体IDeviceIoService
中的函数指针Dispatch
所指向的函数。用户态的应用程序与内核态的驱动程序之间的交互必须使用Dispatch函数。
int32_t (*Dispatch)(struct HdfDeviceIoClient *client, int cmdId, struct HdfSBuf *data, struct HdfSBuf *reply);
这个函数中的代码由驱动开发者负责编写,在应用程序使用驱动服务的时候被HDF调用。HDF调用这个函数的时候,会传进来4个参数:
参数 | 类型 | 描述 |
---|---|---|
client | HdfDeviceIoClient结构体指针 | 这个结构体用于存放HDF设备IO服务客户端的信息。 |
cmdId | 整数 | 来自应用程序的命令字。 |
data | HdfSBuf结构体指针 | 指向一个缓冲区,缓冲区用于存放从应用程序接收的数据。 |
reply | HdfSBuf结构体指针 | 指向一个缓冲区,缓冲区用于存放向应用程序回送的数据。 |
以下是LED驱动程序中的Dispatch
函数的代码:
#define LED_WRITE_READ 1 //应用程序下发给LED驱动的命令字。1:写;0:读。
uint8_t status = 0; //LED的状态。0:灭;1:亮。
//LED的控制数据
enum LedOps {LED_OFF, //点亮LED_ON, //熄灭LED_TOGGLE, //翻转
};
//定义一个结构体,用来存放驱动LED的GPIO端口的编号。
struct Stm32Mp1ILed {uint32_t gpioNum;
};
static struct Stm32Mp1ILed g_Stm32Mp1ILed;
//LED驱动的Dispatch函数
int32_t LedDriverDispatch(struct HdfDeviceIoClient *client, int cmdCode, struct HdfSBuf *data, struct HdfSBuf *reply)
{int ret = HDF_SUCCESS;uint8_t contrl; //用于存放用户态应用程序下发的数据HDF_LOGE("Led driver dispatch");if (client == NULL || client->device == NULL){HDF_LOGE("Led driver device is NULL");return HDF_ERR_INVALID_OBJECT;}switch (cmdCode){/* 接收到用户态应用程序发来的LED_WRITE_READ命令 */case LED_WRITE_READ:/* 从缓冲区data里读取一个8-bit无符号整数,赋值给变量contrl */HdfSbufReadUint8(data, &contrl); /* 控制LED */switch (contrl){/* 开灯 */case LED_ON: GpioWrite(g_Stm32Mp1ILed.gpioNum, GPIO_VAL_LOW);status = 1;break;/* 关灯 */case LED_OFF: GpioWrite(g_Stm32Mp1ILed.gpioNum, GPIO_VAL_HIGH);status = 0;break;/* 状态翻转 */case LED_TOGGLE:if(status == 0){GpioWrite(g_Stm32Mp1ILed.gpioNum, GPIO_VAL_LOW);status = 1;}else{GpioWrite(g_Stm32Mp1ILed.gpioNum, GPIO_VAL_HIGH);status = 0;} break;default:break;}/* 把LED的当前状态值写入缓冲区reply, 可被带至用户程序 */if (!HdfSbufWriteUint8(reply, status)) {HDF_LOGE("replay is fail");return HDF_FAILURE;}break;default:break;}return ret;
}
在这段代码中,使用了HDF提供的以下这些接口函数:
bool HdfSbufReadUint8 (struct HdfSBuf *sbuf, uint8_t *value )
bool HdfSbufWriteUint8(struct HdfSBuf *sbuf, uint8_t value)
在这段代码中,使用了HDF提供的宏HDF_LOGE
,用于向终端打印输出信息。
在这段代码中,还使用了平台设备GPIO的驱动接口,通过改变GPIO端口的输出电平来控制LED灯的亮/灭。
int32_t GpioWrite(uint16_t gpio, uint16_t val)
用户态的应用程序与内核态的驱动程序之间进行交互的时候,常常会用到HDF提供的两种 消息机制 :
(1)第一种消息机制:应用先发送消息给驱动,驱动收到消息后再回应消息给应用。上面Dispatch
函数中的代码使用的就是这种消息机制。
(2)第二种消息机制:驱动主动发消息(上报事件)给应用,应用监听到驱动上报事件后,自动执行回调函数对收到的消息进行处理。注:我将另外写一篇文章讨论并编程测试这种消息机制。
(2)非Dispatch函数
非Dispatch函数,指的是在自定义的驱动服务入口结构体中、在IDeviceIoService
结构体后面添加的那些函数指针所指向的函数。
以下是LED驱动的两个非Dispatch服务函数:
//切换LED的状态
int32_t LedDriverServiceA(void)
{if(status == 0){GpioWrite(g_Stm32Mp1ILed.gpioNum, GPIO_VAL_LOW);status = 1;}else{GpioWrite(g_Stm32Mp1ILed.gpioNum, GPIO_VAL_HIGH);status = 0;} return 0;
}
//打开或关闭LED
int32_t LedDriverServiceB(uint32_t inputCode)
{switch (inputCode){//开灯 case LED_ON: GpioWrite(g_Stm32Mp1ILed.gpioNum, GPIO_VAL_LOW);status = 1;break;//关灯 case LED_OFF: GpioWrite(g_Stm32Mp1ILed.gpioNum, GPIO_VAL_HIGH);status = 0;break;//default:break;}return 0;
}
本文中并没有使用这两个函数。关于如何使用这些非Dispatch服务函数,我将另外写一篇文章进行讨论和编程测试。
2.1.2 驱动入口函数
下面编写LED驱动的三个入口函数:Bind函数、Init函数、Release函数。
1、Bind函数
Bind函数的主要任务就是把驱动服务的入口告知HDF驱动框架,或者说把设备的驱动服务入口与代表设备的HdfDeviceObject
结构体绑定在一起。以下是LED驱动的Bind函数:
// 驱动入口:Bind函数
int32_t HdfLedDriverBind(struct HdfDeviceObject *deviceObject)
{//检查HDF设备对象结构体是否存在if (deviceObject == NULL){HDF_LOGE("Led driver bind failed!");return HDF_ERR_INVALID_OBJECT;}//用自定义的驱动服务入口结构体类型创建驱动服务入口static struct LedDriverService ledDriver = {.ioService.Dispatch = LedDriverDispatch,.ServiceA = LedDriverServiceA,.ServiceB = LedDriverServiceB,} //也可以直接用IDeviceIoService结构体类型创建驱动服务入口/*static struct IDeviceIoService ledDriver = {.Dispatch = LedDriverDispatch,};*///注册驱动服务入口deviceObject->service = (struct IDeviceIoService *)(&ledDriver);HDF_LOGD("Led driver bind success");return HDF_SUCCESS;
}
2、Init函数
Init函数主要是用来完成一些驱动的初始化动作,比如:通过HDF提供的接口获取设备的配置信息、初始化设备、订阅驱动提供的服务等等。
//定义一个结构体,用来存放驱动LED的GPIO端口的编号。
struct Stm32Mp1ILed {uint32_t gpioNum;
};
static struct Stm32Mp1ILed g_Stm32Mp1ILed;
以下是 LED驱动的Init函数:
// 驱动入口:Init函数
int32_t HdfLedDriverInit(struct HdfDeviceObject *device)
{struct Stm32Mp1ILed *led = &g_Stm32Mp1ILed;int32_t ret;/* 检查HDF设备对象结构体里的property字段是否有效 */if (device == NULL || device->property == NULL) {HDF_LOGE("%s: device or property NULL!", __func__);return HDF_ERR_INVALID_OBJECT;}/* 读取LED的配置信息 */ret = Stm32LedReadDrs(led, device->property);if (ret != HDF_SUCCESS) {HDF_LOGE("%s: get led device resource fail:%d", __func__, ret);return ret;}/* 将GPIO管脚配置为输出 */ret = GpioSetDir(led->gpioNum, GPIO_DIR_OUT);if (ret != 0){HDF_LOGE("GpioSerDir: failed, ret %d\n", ret);return ret;}HDF_LOGD("Led driver Init success");return HDF_SUCCESS;
}
在Init函数中调用了一个自定义函数Stm32LedReadDrs
,用于读取LED的配置信息(驱动LED的GPIO端口的编号):
static int32_t Stm32LedReadDrs(struct Stm32Mp1ILed *led, const struct DeviceResourceNode *property)
{int32_t ret;struct DeviceResourceIface *drsOps = NULL;/* 根据设备配置文件的类型HDF_CONFIG_SOURCE,获取DeviceResourceIface结构体的地址。*/drsOps = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE);if (drsOps == NULL || drsOps->GetUint32 == NULL) {HDF_LOGE("%s: invalid drs ops!", __func__);return HDF_FAILURE;}/* 读取LED配置里面的led_gpio_num属性 */ret = drsOps->GetUint32(property, "led_gpio_num", &led->gpioNum, 0); if (ret != HDF_SUCCESS) {HDF_LOGE("%s: read led gpio num fail!", __func__);return ret;}return HDF_SUCCESS;
}
在函数Stm32LedReadDrs
中,使用到了接口函数DeviceResourceGetIfaceInstance
、结构体DeviceResourceIface
,以及结构体DeviceResourceIface
中函数指针GetUint32
所指向的函数。
在Init函数中,调用了更底层的GPIO驱动接口函数GpioSetDir
,将驱动LED的GPIO设置成输出。
3、Release函数
Release函数用来释放驱动所占用资源。以下是LED驱动的Release函数:
// 驱动入口:Release函数
void HdfLedDriverRelease(struct HdfDeviceObject *deviceObject)
{if (deviceObject == NULL){HDF_LOGE("Led driver release failed!");return;}HDF_LOGD("Led driver release success");return;
}
2.1.3 注册驱动
注册驱动包括以下两个步骤:
1、创建驱动入口
定义一个HdfDriverEntry
结构体类型的全局变量。代码如下所示:
// 驱动入口,HdfDriverEntry结构体类型的全局变量
struct HdfDriverEntry g_ledDriverEntry = {.moduleVersion = 1, //驱动的版本号.moduleName = "HDF_LED", //与device_info.hcs中的驱动名称一致.Bind = HdfLedDriverBind,.Init = HdfLedDriverInit,.Release = HdfLedDriverRelease,
};
2、注册驱动入口
用 宏HDF_INIT 将驱动入口注册到HDF驱动框架中,代码如下所示:
// 使用宏HDF_INIT将驱动入口注册到HDF框架中
HDF_INIT(g_ledDriverEntry);
2.2 编译脚本
1、为驱动程序led.c
新建一个编译脚本:device/st/drivers/led/BUILD.gn
,在文件中写入:
import("//drivers/adapter/khdf/liteos/hdf.gni")hdf_driver("hdf_led") {sources = ["led.c",]
}
这个编译脚本指示鸿蒙的编译构建子系统去完成一个类型为hdf_driver
、名为hdf_led
的编译构建目标。sources
列表中是完成这个编译构建目标所需要的源文件。
2、打开上一层编译脚本:device/st/drivers/BUILD.gn
,在deps
列表中加入刚才那个编译构建目标hdf_led
。
上图中的led
是一个相对路径,后面省略了:hdf_led
。
至此,LED灯的驱动程序就开发完了。接下来,在文章《LED的C语言应用程序》中,我们将开发一个LED灯的应用程序来测试一下这个驱动。
基于HDF的LED驱动程序开发(2)相关推荐
- 基于HDF的LED驱动程序开发(1)
引言 本文以小熊派BearPi-HM_Micro_small开发板上的一个LED灯为例,介绍如何基于HDF框架开发一个外设的驱动程序. 在阅读本文之前,建议大家先阅读:<OpenHarmony驱 ...
- 基于AM335X的EDMA 驱动程序开发
0 简介 EDMA即TI自定义的加强版DMA数据传输模式,经过简单的了解,本质上它设计一种非常高效的可以合并多次DMA传输的模式. 1 协议简介 协议的简单介绍可以参考http://blog.chin ...
- 基于RK3399的LED驱动开发
1.添加设备树 在设备树 arch/arm64/boot/dts/rockchip/rk3399-firefly-linux.dts 中添加 gpio-led{status = "okay& ...
- OpenHarmony HDF LED驱动开发 基于小熊派Micro
文章目录 一.效果展示 二.led控制程序 2.1.led驱动程序 2.1.1.驱动程序 2.1.2.驱动配置 2.2.C应用程序 2.3.JS应用 2.3.1.JS代码 2.3.2.C++接口 一. ...
- jz2440_基于平台设备的LED驱动程序
平台设备驱动程序是基于总线系统实现的,由三个部分组成: 总线,这里的总线使用的是内核自带的 platform_bus_type 总线,无需我们进行实现: 设备,这个驱动主要是对 LED 灯的操作,因此 ...
- hc05与单片机连接图_基于proteus的51单片机开发实例(13)-LED指示那个按键被按下
1. 基于proteus的51单片机开发实例(13)-LED指示那个按键被按下 1.1. 实验目的 本实例将实现两路外部中断的检测和识别,让我们能够更好地理解51单片机的外部中断以及中断优先级的概念. ...
- 嵌入式Linux LED,键盘,AD驱动程序开发
LED,键盘,AD驱动程序开发 原文: http://blog.sina.com.cn/s/blog_4083b2d70100bnlf.html 一:硬件平台及系统平台 C ...
- linux-2.6.32在mini2440开发板上移植(16)之LED 驱动程序移植
LED 驱动程序移植 编者:对于led的驱动程序,很多文章都有详细的介绍,我的博客里面有一篇专门详解这个的.需要看的,可以找下.led灯的驱动其实就代表了I/O口的驱动.在linux系统下,操作一个I ...
- 基于2440的Linux开发原理,基于S3C2440和Linux的嵌入式网络驱动程序开发
摘要: 嵌入式与互联网已经成为最热门的技术.使嵌入式系统具备网络功能,并将它们与Internet或企业局域网连接起来,增强了嵌入式系统多方面的实用性. 本文采用S3C2440微处理器为硬件开发平台,根 ...
最新文章
- jQuery对下拉框、单选框、多选框的处理
- Logback Pattern 日志格式配置
- VSTO之旅系列(三):自定义Excel UI
- 模拟调频信号FM解调的matlab和C语言实现
- LeetCode 1630. 等差子数组
- 网络知识普及:双网卡下知识知多少,路由表及网关那点事
- 随想录(网站api的设计)
- UI控件篇——UIPageControl及其自定义
- getconnection java_在MyEclipse用java写的一个GetConnection1.java,用于连接MySQL,却总是出错。(没有财富值了,见谅!)...
- 记一次内存无法回收导致频繁fullgc机器假死的思路
- Struts2的学习笔记1配置运行环境运行第一个例子Hello word!
- java这一年第几天_java 输入年月日,计算该日是这一年的第几天
- 《MySQL DBA修炼之道》——2.2 官方版本的安装
- iqoo9pro和vivox80哪个值得买
- 缓存插件 EHCache
- SpringCloud Alibaba 实战之《服务门户:Spring Cloud Gateway 如何把好微服务的大门》
- 网络负载均衡优化RPS实现简介
- 8月9日个人训练小结
- 解决IntelliJIdea许可证过期,输入许可证认证不成功
- 《MEMORY NETWORKS》翻译 Jason Weston, Sumit Chopra Antoine Bordes