前言
现在有两种方式实现GPIO的控制:
方式一:通过register_driver的方式,注册file_operations_vfs的一些驱动操作接口,比如open、close、read、write、ioctl等等。这个方法几乎和Linux的驱动接口一模一样,很方便有Linux驱动经验的人员开发。
方式二:通过hdf服务的方式,应用通过服务名称获取服务句柄,然后通过API接口直接获取或者订阅该服务对应的驱动事件。用户空间将数据放在一段共享内存(HdfSBuf),然后将数据和cmd编号发送给内核空间的驱动;驱动也可以触发一个事件,通过一段共享内存(HdfSBuf)将内核空间的数据传递给用户空间,该事件会通知用户空间回调函数进行相应处理。
方式一的方法已经有官方和发烧友的参考例程了,这里就不单独来写了,本文将基于方式二进行说明。看起来方式一和方式二似乎是可以共存的,这个测试可能要放到后面的追加文章了。而且还有驱动如何从hcs配置文件里获取板级配置也还待确定一下。
1. 硬件
(1) LED_light/GPIO2_3
灯板上的D4 LED。


(2) GPIO5_1/UART4_TXD
补光灯。

(3) LSADC_CH0/GPIO10_3
红外检测。

首先需要选取一个空闲的GPIO管脚,本例程基于Hi3516DV300某开发板,GPIO管脚选择GPIO10_3,换算成GPIO号为83。
Hi3516DV300控制器管理12组GPIO管脚,每组8个。
GPIO号 = GPIO组索引 (0~11) * 每组GPIO管脚数(8) + 组内偏移
举例:GPIO10_3的GPIO号 = 10 * 8 + 3 = 83
编写软件:
2. 在HDF框架的配置文件中添加该驱动的配置信息,如下所示:
$ vi ~/harmony/sdk/vendor/hisi/hi35xx/hi3516dv300/config/device_info/device_info.hcs
3. 编写驱动代码
3.1 基于HDF框架编写的gpio驱动代码如下:
$ mkdir -p ~/harmony/sdk/vendor/huawei/hdf/sample/platform/gpio-sample
$ vi ~/harmony/sdk/vendor/huawei/hdf/sample/platform/gpio-sample/gpio_sample.c
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include "hdf_log.h"
#include "hdf_base.h"
#include "hdf_device_desc.h"
#include "gpio_if.h"
#include "osal_io.h"
#include "osal_irq.h"
#include "osal_time.h"
#define HDF_LOG_TAG gpio_driver
// 设置cmd编号,类似于Linux的ioctl命令码。
#define CMD_GPIO_LDR_TEST 100
#define CMD_GPIO_LED_TEST 101
#define CMD_GPIO_FILL_LIGHT 102
// GPIO NUM
#define GPIO_NUM_LDR_GPIO10_3 83 // 10*8+3
#define GPIO_NUM_LED_GPIO2_3 19 // 2*8+3
#define GPIO_NUM_FILL_LIGHT_GPIO5_1 41 // 5*8+1
static uint32_t g_irqCnt;
// 中断服务函数
static int32_t TestCaseGpioIrqHandler(uint16_t gpio, void *data)
{
HDF_LOGE("%s: irq triggered! on gpio:%u, data=%p", __func__, gpio, data);
g_irqCnt++; // 如果中断服务函数触发执行,则将全局中断计数加1
return GpioDisableIrq(gpio);
}
// 测试用例函数
static int32_t gpio_test_ldr_irq_edge(void)
{
int32_t ret;
uint16_t valRead;
uint16_t mode;
uint16_t gpio = GPIO_NUM_LDR_GPIO10_3; // 待测试的GPIO管脚号
uint32_t timeout;
// 将管脚方向设置为输出
ret = GpioSetDir(gpio, GPIO_DIR_OUT);
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: set dir fail! ret:%d\n", __func__, ret);
return ret;
}
// 先禁止该管脚中断
ret = GpioDisableIrq(gpio);
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: disable irq fail! ret:%d\n", __func__, ret);
return ret;
}
// 为管脚设置中断服务函数,触发模式为上升沿和下降沿共同触发
mode = OSAL_IRQF_TRIGGER_RISING | OSAL_IRQF_TRIGGER_FALLING;
HDF_LOGE("%s: mode:%0x\n", __func__, mode);
ret = GpioSetIrq(gpio, mode, TestCaseGpioIrqHandler, NULL);
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: set irq fail! ret:%d\n", __func__, ret);
return ret;
}
// 使能此管脚中断
ret = GpioEnableIrq(gpio);
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: enable irq fail! ret:%d\n", __func__, ret);
(void)GpioUnSetIrq(gpio);
return ret;
}
g_irqCnt = 0; // 清除全局计数器
timeout = 0; // 等待时间清零
// 等待此管脚中断服务函数触发,等待超时时间为1000毫秒
while (g_irqCnt <= 0 && timeout < 1000) {
(void)GpioRead(gpio, &valRead);
(void)GpioWrite(gpio, (valRead == GPIO_VAL_LOW) ? GPIO_VAL_HIGH : GPIO_VAL_LOW);
HDF_LOGE("%s: wait irq timeout:%u\n", __func__, timeout);
OsalMDelay(200); // wait for irq trigger
timeout += 200;
}
(void)GpioUnSetIrq(gpio);
return (g_irqCnt > 0) ? HDF_SUCCESS : HDF_FAILURE;
}
static void gpio_test_led_blink(uint16_t gpio)
{
int32_t ret;
int i = 0;
// 将管脚方向设置为输出
ret = GpioSetDir(gpio, GPIO_DIR_OUT);
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: set dir fail! ret:%d\n", __func__, ret);
}
//HDF_LOGE("\nGpioSetDir GPIO_DIR_OUT");
for (i = 0; i < 3; i++) {
ret = GpioWrite(gpio, GPIO_VAL_HIGH);
if (ret != 0) {
HDF_LOGE("GpioWrite: failed, ret %d\n", ret);
}
//HDF_LOGE("GpioWrite GPIO_VAL_HIGH");
//OsalMDelay(500); // 500ms, but OsalMDelay work is abnormal.
OsalMSleep(500);
ret = GpioWrite(gpio, GPIO_VAL_LOW);
if (ret != 0) {
HDF_LOGE("GpioWrite: failed, ret %d\n", ret);
}
//HDF_LOGE("GpioWrite GPIO_VAL_LOW");
//OsalMDelay(500); // 500ms
OsalMSleep(500);
}
}
// Dispatch是驱动中用来处理用户态发下来的消息的函数。
static int32_t GpioSampleDriverDispatch(struct HdfDeviceIoClient *deviceIoClient, int id, struct HdfSBuf *data, struct HdfSBuf *reply)
{
HDF_LOGE("%s: received cmd %d", __func__, id);
switch (id) {
case CMD_GPIO_LDR_TEST: { // 通常会采用switch case的方式来写。
const char *readData = HdfSbufReadString(data); // 内核从用户空间获取数据,类似Linux的copy_from_user。
if (readData != NULL) {
HDF_LOGE("%s: read data is: %s", __func__, readData); // 内核打印从用户空间独到的数据。
}
// GPIO中断检测
gpio_test_ldr_irq_edge();
// 将向用户空间返回内容修改为字符串
char *sendData = "come from kernel sapce: CMD_GPIO_LDR_TEST";
if (!HdfSbufWriteString(reply, sendData)) {
HDF_LOGE("%s: fail to write sbuf", __func__);
}
// 通HdfDeviceSendEvent函数触发用户空间获取驱动上报数据。
return HdfDeviceSendEvent(deviceIoClient->device, id, data);
}
case CMD_GPIO_LED_TEST: {
const char *readData = HdfSbufReadString(data); // 内核从用户空间获取数据,类似Linux的copy_from_user。
if (readData != NULL) {
HDF_LOGE("%s: read data is: %s", __func__, readData); // 内核打印从用户空间独到>的数据。
}
// GPIO test
gpio_test_led_blink(GPIO_NUM_LED_GPIO2_3);
// 将向用户空间返回内容修改为字符串
char *sendData = "come from kernel sapce: CMD_GPIO_LED_TEST";
if (!HdfSbufWriteString(reply, sendData)) {
HDF_LOGE("%s: fail to write sbuf", __func__);
}
// 通HdfDeviceSendEvent函数触发用户空间获取驱动上报数据。
return HdfDeviceSendEvent(deviceIoClient->device, id, data);
}
case CMD_GPIO_FILL_LIGHT: {
const char *readData = HdfSbufReadString(data); // 内核从用户空间获取数据,类似Linux的copy_from_user。
if (readData != NULL) {
HDF_LOGE("%s: read data is: %s", __func__, readData); // 内核打印从用户空间独到>的数据。
}
// GPIO test
gpio_test_led_blink(GPIO_NUM_FILL_LIGHT_GPIO5_1);
// 将向用户空间返回内容修改为字符串
char *sendData = "come from kernel sapce: CMD_GPIO_FILL_LIGHT";
if (!HdfSbufWriteString(reply, sendData)) {
HDF_LOGE("%s: fail to write sbuf", __func__);
}
// 通HdfDeviceSendEvent函数触发用户空间获取驱动上报数据。
return HdfDeviceSendEvent(deviceIoClient->device, id, data);
}
default: {
HDF_LOGE("no such cmd id");
break;
}
}
return HDF_FAILURE;
}
// 驱动资源释放的接口,本例未分配需要回收的资源,因此为空。
static void GpioSampleDriverRelease(struct HdfDeviceObject *deviceObject)
{
// release resources here
return;
}
// 用户态获取驱动的服务,获取该服务之后通过服务中的Dispatch方法向驱动发送消息。
static int GpioSampleDriverBind(struct HdfDeviceObject *deviceObject)
{
if (deviceObject == NULL) {
return HDF_FAILURE;
}
static struct IDeviceIoService testService = {
// Dispatch是用来处理用户态发下来的消息。
.Dispatch = GpioSampleDriverDispatch,
};
deviceObject->service = &testService;
return HDF_SUCCESS;
}
// 驱动自身业务初始的接口。
static int GpioSampleDriverInit(struct HdfDeviceObject *deviceObject)
{
if (deviceObject == NULL) {
HDF_LOGE("%s::ptr is null!", __func__);
return HDF_FAILURE;
}
HDF_LOGE("gpio driver Init success");
return HDF_SUCCESS;
}
// 定义驱动入口的对象,必须为HdfDriverEntry(在hdf_device_desc.h中定义)类型的全局变量。
struct HdfDriverEntry g_gpioSampleDriverEntry = {
.moduleVersion = 1,
.moduleName = "gpio_driver", // 驱动的关键名称。
.Bind = GpioSampleDriverBind,
.Init = GpioSampleDriverInit,
.Release = GpioSampleDriverRelease,
};
// 调用HDF_INIT将驱动入口注册到HDF框架中,在加载驱动时HDF框架会先调用Bind函数,
// 再调用Init函数加载该驱动,当Init调用异常时,HDF框架会调用Release释放驱动资源并退出。
HDF_INIT(g_gpioSampleDriverEntry);
- 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.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
- 97.
- 98.
- 99.
- 100.
- 101.
- 102.
- 103.
- 104.
- 105.
- 106.
- 107.
- 108.
- 109.
- 110.
- 111.
- 112.
- 113.
- 114.
- 115.
- 116.
- 117.
- 118.
- 119.
- 120.
- 121.
- 122.
- 123.
- 124.
- 125.
- 126.
- 127.
- 128.
- 129.
- 130.
- 131.
- 132.
- 133.
- 134.
- 135.
- 136.
- 137.
- 138.
- 139.
- 140.
- 141.
- 142.
- 143.
- 144.
- 145.
- 146.
- 147.
- 148.
- 149.
- 150.
- 151.
- 152.
- 153.
- 154.
- 155.
- 156.
- 157.
- 158.
- 159.
- 160.
- 161.
- 162.
- 163.
- 164.
- 165.
- 166.
- 167.
- 168.
- 169.
- 170.
- 171.
- 172.
- 173.
- 174.
- 175.
- 176.
- 177.
- 178.
- 179.
- 180.
- 181.
- 182.
- 183.
- 184.
- 185.
- 186.
- 187.
- 188.
- 189.
- 190.
- 191.
- 192.
- 193.
- 194.
- 195.
- 196.
- 197.
- 198.
- 199.
- 200.
- 201.
- 202.
- 203.
- 204.
- 205.
- 206.
- 207.
- 208.
- 209.
- 210.
- 211.
- 212.
- 213.
- 214.
- 215.
- 216.
- 217.
- 218.
- 219.
- 220.
- 221.
- 222.
- 223.
- 224.
- 225.
- 226.
- 227.
- 228.
- 229.
- 230.
- 231.
- 232.
- 233.
- 234.
- 235.
3.2 创建Kconfig
$ cd ~/harmony/sdk/vendor/huawei/hdf/sample/platform/gpio-sample
$ touch Kconfig && vi Kconfig
修改上层Kconfig
$ vi ~/harmony/sdk/vendor/huawei/hdf/Kconfig
3.3 创建Makefile
$ cd ~/harmony/sdk/vendor/huawei/hdf/sample/platform/gpio-sample
$ touch Makefile && vi Makefile
3.4 将驱动模块加入编译
$ vi ~/harmony/sdk/vendor/huawei/hdf/hdf_vendor.mk
LITEOS_BASELIB中的hdf_sample为Makefile里的MODULE_NAME名字。
3.5 驱动编译
3.5.1 手动使能驱动源码编译:
$ vi ~/harmony/sdk/kernel/liteos_a/tools/build/config/debug/hi3516dv300_clang.config
让修改生效:
$ cd ~/harmony/sdk/kernel/liteos_a/
$ ./build.sh hi3516dv300 clang debug
3.5.2 编译
$ cd ~/harmony/sdk/
$ python build.py ipcamera_hi3516dv300 -b debug
3.5.3 查看驱动模块gpio_sample存放位置:
~/harmony/sdk$ find -name gpio_sample
4. 编写用户程序和驱动交互代码
4.1 基于HDF框架编写的用户态程序和驱动交互的代码如下:
$ mkdir -p ~/harmony/sdk/vendor/huawei/hdf/sample/platform/gpio-sample/app
$ vi ~/harmony/sdk/vendor/huawei/hdf/sample/platform/gpio-sample/app/gpio_sample_app.c
4.2 编写编译APP源码的BUILD.gn文件
$ vi ~/harmony/sdk/vendor/huawei/hdf/sample/platform/gpio-sample/app/BUILD.gn
4.3 将hdf_sample_app.c加入编译
$ vi ~/harmony/sdk/build/lite/product/ipcamera_hi3516dv300.json
4.4 应用编译
$ python build.py ipcamera_hi3516dv300 -b debug
$ vi out/ipcamera_hi3516dv300/build.log
5. 烧录:
5.1 将系统bin文件拷贝到Windows共享目录中:
$ rm /mnt/hgfs/proj-harmony/images/out/ -RF
$ cp -rf out/ /mnt/hgfs/proj-harmony/images/
5.2 在HiTool工具中依次填入OHOS_Image.bin、rootfs.img、userfs.img的文件位置
E:\Share_Space\proj-harmony\images\out\ipcamera_hi3516dv300

操作流程如下:

解决windows-x86-64的PL2303驱动开机后无法使用的办法:每次开机后,在设备管理中找到有问题的设备,鼠标右键先卸载驱动,安装USB-to-Serial Comm Port.exe,要注意的是需要赶在驱动失效前用HiTool将串口占用,系统就不会提示驱动有问题了。
修改一下长度:

点击“烧写”按键后,控制台会提示重启目标板,重启后,系统就自动进入烧写了。
单板初次启动需要修改启动参数,重新上电后登陆会进入uboot中,如果分区位置没有变化则不用执行下面修改启动参数的指令。
hisilicon # setenv bootcmd "mmc read 0x0 0x80000000 0x800 0x3000; go 0x80000000";
hisilicon # setenv bootargs "console=ttyAMA0,115200n8 root=emmc fstype=vfat rootaddr=7M rootsize=15M rw";
hisilicon # saveenv
hisilicon # reset
重启后进入系统。
注释:表示设置启动参数,输出模式为串口输出,波特率为115200,数据位8,rootfs挂载于emmc器件,文件系统类型为vfat,“rootaddr=6M rootsize=14M rw”处对应填入rootfs.img的烧写起始位置与长度,此处与新增rootfs.img文件时所填大小必须相同。
6. 测试
在根目录下,在命令行输入下面命令执行写入的demo程序,显示成功结果如下所示。没执行一次gpio_sample_app将会触发1s时长的红外检测,超时将会自动退出检测。
打印结果的截图:
帮助信息:

gpio中断检测成功:

gpio中断检测超时失败:

D4-LED控制:此时D4和处理器所在板子上的两颗绿色LED会闪烁三次。

补光灯控制:此时两颗红色补光灯会闪烁三次,之所以出现红色可能是该灯需要pwm控制,然后直接控制电平时输入电流太小导致的,这个也暂时没去验证,反正能亮灭就达到了测试目的。

参考文档:
● GPIO链接:https://gitee.com/openharmony/docs/blob/master/driver/GPIO.md
● GPIO概述
● GPIO使用指导
● GPIO使用实例
● Touchscreen开发实例 https://gitee.com/openharmony/docs/blob/master/driver/Touchscreen%E5%BC%80%E5%8F%91%E5%AE%9E%E4%BE%8B.md
参考该源码实现类似linux的文件操作接口:file_operations_vfs。