• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 编写点亮LED灯程序
2
3在本示例将演示如何在开发板上运行一个控制LED灯的程序,达到能关闭灯、开启灯以及翻转灯的状态。希望通过本教程的学习,开发者能掌握如何开发一个设备驱动,以及如何在应用层调用驱动。
4
5**注:** 在学习本教程之前,请确保已经熟练掌握[如何编写一个hello_world应用](./编写一个hello_world程序.md),以下教程篇幅较长,请耐心仔细阅读。
6
7## 任务介绍
8点亮LED主要包含以下任务:
9
101. LED驱动代码开发
11
12    驱动开发主要包含以下工作
13
14    - 编写驱动代码
15    - 编写驱动编译文件
16    - 编写驱动配置文件
17
182. 点亮LED业务代码开发
19
20    业务代码开发主要包含以下工作
21    - 编写业务代码
22    -    编写业务代码编译文件
23
24    ![](figures/点亮LED导图.png)
25
26
27
28## 一、LED灯驱动开发
29
301. HDF驱动简介
31
32    HDF框架以组件化的驱动模型作为核心设计思路,为开发者提供更精细化的驱动管理,让驱动开发和部署更加规范。HDF框架将一类设备驱动放在同一个host里面,开发者也可以将驱动功能分层独立开发和部署,支持一个驱动多个node,HDF驱动模型如下图所示:
33
34    ![](figures/zh-cn_image_0000001054564784.png)
35
362.  确定目录结构。
37
38    在device\soc\st\common\platform路径下新建led文件夹,并创建驱动文件led.c和编译构建文件BUILD.gn、Makefile。
39
40    ```
41    .
42    └── device
43        └── soc
44            └── st
45                └── common
46                   └── platform
47                        └── led
48                            │── led.c
49                            └── BUILD.gn
50                            └── Makefile
51
52    ```
53
54    在device\soc\st\stm32mp1xx\sdk_liteos\hdf_config路径下新建led文件夹,并创建驱动配置文件led_config.hcs55    ```
56    .
57    └── device
58        └── soc
59            └── st
60                └── stm32mp1xx
61                    └── sdk_liteos
62                        └── hdf_config
63                            └── led
64                                │── led_config.hcs
65    ```
66
673. LED驱动实现
68
69    驱动实现包含驱动业务代码和驱动入口注册,在led.c文件中添加以下代码:
70
71    ```
72    #include "hdf_device_desc.h"
73    #include "hdf_log.h"
74    #include "device_resource_if.h"
75    #include "osal_io.h"
76    #include "osal.h"
77    #include "osal_mem.h"
78    #include "gpio_if.h"
79
80    #define HDF_LOG_TAG led_driver // 打印日志所包含的标签,如果不定义则用默认定义的HDF_TAG标签
81    #define LED_WRITE_READ 1       // 读写操作码1
82
83    enum LedOps {
84        LED_OFF,
85        LED_ON,
86        LED_TOGGLE,
87    };
88
89    struct Stm32Mp1ILed {
90        uint32_t gpioNum;
91    };
92    static struct Stm32Mp1ILed g_Stm32Mp1ILed;
93    uint8_t status = 0;
94    // Dispatch是用来处理用户态发下来的消息
95    int32_t LedDriverDispatch(struct HdfDeviceIoClient *client, int cmdCode, struct HdfSBuf *data, struct HdfSBuf *reply)
96    {
97        uint8_t contrl;
98        HDF_LOGE("Led driver dispatch");
99        if (client == NULL || client->device == NULL)
100        {
101            HDF_LOGE("Led driver device is NULL");
102            return HDF_ERR_INVALID_OBJECT;
103        }
104
105        switch (cmdCode)
106        {
107        /* 接收到用户态发来的LED_WRITE_READ命令 */
108        case LED_WRITE_READ:
109            /* 读取data里的数据,赋值给contrl */
110            HdfSbufReadUint8(data,&contrl);
111            switch (contrl)
112            {
113            /* 开灯 */
114            case LED_ON:
115                GpioWrite(g_Stm32Mp1ILed.gpioNum, GPIO_VAL_LOW);
116                status = 1;
117                break;
118            /* 关灯 */
119            case LED_OFF:
120                GpioWrite(g_Stm32Mp1ILed.gpioNum, GPIO_VAL_HIGH);
121                status = 0;
122                break;
123            /* 状态翻转 */
124            case LED_TOGGLE:
125                if(status == 0)
126                {
127                    GpioWrite(g_Stm32Mp1ILed.gpioNum, GPIO_VAL_LOW);
128                    status = 1;
129                }
130                else
131                {
132                    GpioWrite(g_Stm32Mp1ILed.gpioNum, GPIO_VAL_HIGH);
133                    status = 0;
134                }
135                break;
136            default:
137                break;
138            }
139            /* 把LED的状态值写入reply, 可被带至用户程序 */
140            if (!HdfSbufWriteInt32(reply, status))
141            {
142                HDF_LOGE("replay is fail");
143                return HDF_FAILURE;
144            }
145            break;
146        default:
147            break;
148        }
149        return HDF_SUCCESS;
150    }
151
152    // 读取驱动私有配置
153    static int32_t Stm32LedReadDrs(struct Stm32Mp1ILed *led, const struct DeviceResourceNode *node)
154    {
155        int32_t ret;
156        struct DeviceResourceIface *drsOps = NULL;
157
158        drsOps = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE);
159        if (drsOps == NULL || drsOps->GetUint32 == NULL) {
160            HDF_LOGE("%s: invalid drs ops!", __func__);
161            return HDF_FAILURE;
162        }
163        /* 读取led.hcs里面led_gpio_num的值 */
164        ret = drsOps->GetUint32(node, "led_gpio_num", &led->gpioNum, 0);
165        if (ret != HDF_SUCCESS) {
166            HDF_LOGE("%s: read led gpio num fail!", __func__);
167            return ret;
168        }
169        return HDF_SUCCESS;
170    }
171
172    //驱动对外提供的服务能力,将相关的服务接口绑定到HDF框架
173    int32_t HdfLedDriverBind(struct HdfDeviceObject *deviceObject)
174    {
175        if (deviceObject == NULL)
176        {
177            HDF_LOGE("Led driver bind failed!");
178            return HDF_ERR_INVALID_OBJECT;
179        }
180        static struct IDeviceIoService ledDriver = {
181            .Dispatch = LedDriverDispatch,
182        };
183        deviceObject->service = (struct IDeviceIoService *)(&ledDriver);
184        HDF_LOGD("Led driver bind success");
185        return HDF_SUCCESS;
186    }
187
188    // 驱动自身业务初始的接口
189    int32_t HdfLedDriverInit(struct HdfDeviceObject *device)
190    {
191        struct Stm32Mp1ILed *led = &g_Stm32Mp1ILed;
192        int32_t ret;
193
194        if (device == NULL || device->property == NULL) {
195            HDF_LOGE("%s: device or property NULL!", __func__);
196            return HDF_ERR_INVALID_OBJECT;
197        }
198        /* 读取hcs私有属性值 */
199        ret = Stm32LedReadDrs(led, device->property);
200        if (ret != HDF_SUCCESS) {
201            HDF_LOGE("%s: get led device resource fail:%d", __func__, ret);
202            return ret;
203        }
204        /* 将GPIO管脚配置为输出 */
205        ret = GpioSetDir(led->gpioNum, GPIO_DIR_OUT);
206        if (ret != 0)
207        {
208            HDF_LOGE("GpioSerDir: failed, ret %d\n", ret);
209            return ret;
210        }
211        HDF_LOGD("Led driver Init success");
212        return HDF_SUCCESS;
213    }
214
215    // 驱动资源释放的接口
216    void HdfLedDriverRelease(struct HdfDeviceObject *deviceObject)
217    {
218        if (deviceObject == NULL)
219        {
220            HDF_LOGE("Led driver release failed!");
221            return;
222        }
223        HDF_LOGD("Led driver release success");
224        return;
225    }
226
227    // 定义驱动入口的对象,必须为HdfDriverEntry(在hdf_device_desc.h中定义)类型的全局变量
228    struct HdfDriverEntry g_ledDriverEntry = {
229        .moduleVersion = 1,
230        .moduleName = "HDF_LED",
231        .Bind = HdfLedDriverBind,
232        .Init = HdfLedDriverInit,
233        .Release = HdfLedDriverRelease,
234    };
235
236    // 调用HDF_INIT将驱动入口注册到HDF框架中
237    HDF_INIT(g_ledDriverEntry);
238
239
240    ```
2414. 驱动编译
242
243led/BUILD.gn文件中添加以下代码,将led.c编译成hdf_led。
244    ```
245    import("//drivers/hdf_core/adapter/khdf/liteos/hdf.gni")
246
247    hdf_driver("hdf_led") {
248        sources = [
249        "led.c",
250        ]
251    }
252
253    ```
254
255led/Makefile文件中添加以下代码,将led.c编译成hdf_led。
256    ```
257    include $(LITEOSTOPDIR)/config.mk
258    include $(LITEOSTOPDIR)/../../drivers/hdf_core/adapter/khdf/liteos/lite.mk
259
260    MODULE_NAME := hdf_led
261
262    LOCAL_CFLAGS += $(HDF_INCLUDE)
263
264    LOCAL_SRCS += led.c
265
266    LOCAL_CFLAGS += -fstack-protector-strong -Wextra -Wall -Werror -fsigned-char -fno-strict-aliasing -fno-common
267
268    include $(HDF_DRIVER)
269    ```
270
271/device/soc/st/common/platform/BUILD.gn文件中添加以下代码,将hdf_led编译进内核。"##start##"和"##end##"之间为新增配置("##start##"和"##end##"仅用来标识位置,添加完配置后删除这两行)
272    ```
273    import("//drivers/hdf_core/adapter/khdf/liteos/hdf.gni")
274
275    group("drivers") {
276    deps = [
277        "uart",
278        "iwdg",
279        "i2c",
280        "gpio",
281        ##start##
282        "led",
283        ##end##
284        "stm32mp1xx_hal",
285    ]
286    }
287    ```
288
289/device/soc/st/common/platform/lite.mk文件中添加以下代码,将hdf_led编译进内核。
290    ```
291    LITEOS_BASELIB += -lhdf_led
292    LIB_SUBDIRS    += $(ST_DRIVERS_ROOT)/led
293    ```
2945. 驱动配置
295
296    HDF使用HCS作为配置描述源码,HCS详细介绍参考配置管理介绍。
297
298    驱动配置包含两部分,HDF框架定义的驱动设备描述和驱动的私有配置信息,具体写法如下:
299
300    -   驱动设备描述
301
302        HDF框架加载驱动所需要的信息来源于HDF框架定义的驱动设备描述,因此基于HDF框架开发的驱动必须要在HDF框架定义的device_info.hcs配置文件中添加对应的设备描述,所以我们需要在vendor\bearpi\bearpi_hm_micro\hdf_config\device_info\device_info.hcs中添加LED设备描述。 "##start##"和"##end##"之间为新增配置("##start##"和"##end##"仅用来标识位置,添加完配置后删除这两行)
303        ```
304        platform :: host {
305        hostName = "platform_host";
306        priority = 50;
307        ##start##
308        device_led :: device {                  // led设备节点
309            device0 :: deviceNode {             // led驱动的DeviceNode节点
310                policy = 2;                     // policy字段是驱动服务发布的策略,在驱动服务管理章节有详细介绍
311                priority = 10;                 // 驱动启动优先级(0-200),值越大优先级越低,建议默认配100,优先级相同则不保证device的加载顺序
312                preload = 1;                    // 驱动按需加载字段
313                permission = 0777;              // 驱动创建设备节点权限
314                moduleName = "HDF_LED";        // 驱动名称,该字段的值必须和驱动入口结构的moduleName值一致
315                serviceName = "hdf_led";    // 驱动对外发布服务的名称,必须唯一
316                deviceMatchAttr = "st_stm32mp157_led"; // 驱动私有数据匹配的关键字,必须和驱动私有数据配置表中的match_attr值相等
317            }
318        }
319        ##end##
320        device_gpio :: device {
321            device0 :: deviceNode {
322                policy = 0;
323                priority = 10;
324                permission = 0644;
325                moduleName = "HDF_PLATFORM_GPIO";
326                serviceName = "HDF_PLATFORM_GPIO";
327                deviceMatchAttr = "st_stm32mp157_gpio";
328            }
329        }
330        ```
331    -   驱动私有配置信息
332
333        如果驱动有私有配置,则可以添加一个驱动的配置文件,用来填写一些驱动的默认配置信息,HDF框架在加载驱动的时候,会将对应的配置信息获取并保存在HdfDeviceObject 中的property里面,通过Bind和Init传递给驱动,所以我们需要在device\soc\st\stm32mp1xx\sdk_liteos\hdf_config\led\led_config.hcs中添加LED私有配置描述。
334
335        ```
336        root {
337            LedDriverConfig {
338                led_gpio_num = 13;
339                match_attr = "st_stm32mp157_led";   //该字段的值必须和device_info.hcs中的deviceMatchAttr值一致
340            }
341        }
342        ```
343
344
345
346        配置信息定义之后,需要将该配置文件添加到板级配置入口文件vendor\bearpi\bearpi_hm_micro\hdf_config\hdf.hcs,示例如下:
347
348        ```
349        #include "../../../../device/soc/st/stm32mp1xx/sdk_liteos/hdf_config/led/led_config.hcs"
350        ```
351    - 小结
352        1. device_info.hcs文件中的moduleName必须要和驱动文件中的moduleName字段匹配,这样驱动才会加载起来
353        2. device_info.hcs文件中的deviceMatchAttr的字段必须和私有配置文件中led_config.hcs的match_attr的字段匹配,这样私有配置才能生效
354
355        ![](figures/驱动配置.png)
356
357## 二、点亮LED灯业务代码<a name="section9360141181414"></a>
358
359
3601.  <a name="li5479332115116"></a>确定目录结构。
361
362    开发者编写业务时,务必先在./device/board/bearpi/bearpi\_hm\_micro/app路径下新建一个目录(或一套目录结构),用于存放业务源码文件。
363
364    例如:在app下新增业务my\_led\_app,其中my\_led\_app.c为业务代码,BUILD.gn为编译脚本,具体规划目录结构如下:
365
366    ```
367    .
368    └── device
369        └── board
370            └── bearpi
371                └── bearpi_hm_micro
372                    └── app
373                        │── my_led_app
374                            │── my_led_app.c
375                            └── BUILD.gn
376
377    ```
378
3792.  编写业务代码。
380
381my_led_app.c中添加以下业务代码:
382
383    ```
384    #include <fcntl.h>
385    #include <sys/stat.h>
386    #include <sys/ioctl.h>
387    #include <unistd.h>
388    #include <stdio.h>
389    #include "hdf_sbuf.h"
390    #include "hdf_io_service_if.h"
391
392    #define LED_WRITE_READ 1
393    #define LED_SERVICE "hdf_led"
394
395    static int SendEvent(struct HdfIoService *serv, uint8_t eventData)
396    {
397        int ret = 0;
398        struct HdfSBuf *data = HdfSBufObtainDefaultSize();
399        if (data == NULL)
400        {
401            printf("fail to obtain sbuf data!\r\n");
402            return 1;
403        }
404
405        struct HdfSBuf *reply = HdfSBufObtainDefaultSize();
406        if (reply == NULL)
407        {
408            printf("fail to obtain sbuf reply!\r\n");
409            ret = HDF_DEV_ERR_NO_MEMORY;
410            goto out;
411        }
412        /* 写入数据 */
413        if (!HdfSbufWriteUint8(data, eventData))
414        {
415            printf("fail to write sbuf!\r\n");
416            ret = HDF_FAILURE;
417            goto out;
418        }
419        /* 通过Dispatch发送到驱动 */
420        ret = serv->dispatcher->Dispatch(&serv->object, LED_WRITE_READ, data, reply);
421        if (ret != HDF_SUCCESS)
422        {
423            printf("fail to send service call!\r\n");
424            goto out;
425        }
426
427        int replyData = 0;
428        /* 读取驱动的回复数据 */
429        if (!HdfSbufReadInt32(reply, &replyData))
430        {
431            printf("fail to get service call reply!\r\n");
432            ret = HDF_ERR_INVALID_OBJECT;
433            goto out;
434        }
435        printf("\r\nGet reply is: %d\r\n", replyData);
436    out:
437        HdfSBufRecycle(data);
438        HdfSBufRecycle(reply);
439        return ret;
440    }
441
442    int main(int argc, char **argv)
443    {
444        int i;
445
446        /* 获取服务 */
447        struct HdfIoService *serv = HdfIoServiceBind(LED_SERVICE);
448        if (serv == NULL)
449        {
450            printf("fail to get service %s!\r\n", LED_SERVICE);
451            return HDF_FAILURE;
452        }
453
454        for (i=0; i < argc; i++)
455        {
456            printf("\r\nArgument %d is %s.\r\n", i, argv[i]);
457        }
458
459        SendEvent(serv, atoi(argv[1]));
460
461        HdfIoServiceRecycle(serv);
462        printf("exit");
463
464        return HDF_SUCCESS;
465    }
466    ```
467
4683.  编写将构建业务代码的BUILD.gn文件。
469
470    BUILD.gn文件由三部分内容(目标、源文件、头文件路径)构成,需由开发者完成填写。以my\_led\_app为例,需要创建./device/board/bearpi/bearpi_hm_micro/app/my\_led\_app/BUILD.gn,并完如下配置。
471
472    ```
473    import("//build/lite/config/component/lite_component.gni")
474
475    HDF_FRAMEWORKS = "//drivers/hdf_core/framework"
476
477    executable("led_lib") {
478        output_name = "my_led"
479        sources = [
480            "my_led_app.c",
481        ]
482
483        include_dirs = [
484        "$HDF_FRAMEWORKS/ability/sbuf/include",
485        "$HDF_FRAMEWORKS/core/shared/include",
486        "$HDF_FRAMEWORKS/core/host/include",
487        "$HDF_FRAMEWORKS/core/master/include",
488        "$HDF_FRAMEWORKS/include/core",
489        "$HDF_FRAMEWORKS/include/utils",
490        "$HDF_FRAMEWORKS/utils/include",
491        "$HDF_FRAMEWORKS/include/osal",
492        "//drivers/hdf_core/adapter/uhdf/posix/include",
493        "//third_party/bounds_checking_function/include",
494        "//base/hiviewdfx/hilog_lite/interfaces/native/innerkits",
495        ]
496
497        deps = [
498            "//base/hiviewdfx/hilog_lite/frameworks/featured:hilog_shared",
499            "//drivers/hdf_core/adapter/uhdf/manager:hdf_core",
500            "//drivers/hdf_core/adapter/uhdf/posix:hdf_posix_osal",
501        ]
502    }
503
504    lite_component("my_led_app") {
505        features = [
506            ":led_lib",
507        ]
508    }
509    ```
510
511    -   首先导入 gni 组件,将源码my_led_app.c编译成led_lib库文件
512    -   输出的可执行文件名称由 output_name 定义为my_led
513    -   include_dirs 里面加入my_led_app.c里面需要用到的.h的头文件路径
514    -   deps 里面加入所依赖的库。
515    -   然后将led_lib打包成 lite_component,命名为my_led_app组件。
516
5174. 修改bundle.json配置文件
518
519    修改文件./device/board/bearpi/bearpi_hm_micro/app/bundle.json,新增编译my\_led\_app条目,如下所示,"##start##"和"##end##"之间为新增条目("##start##"和"##end##"仅用来标识位置,添加完配置后删除这两行):
520
521    ```
522    {
523        "sub_component": [
524          "//device/board/bearpi/bearpi_hm_micro/app/launcher:launcher_hap",
525          "//device/board/bearpi/bearpi_hm_micro/app/setting:setting_hap",
526          "//device/board/bearpi/bearpi_hm_micro/app/screensaver:screensaver_hap",
527          "//device/board/bearpi/bearpi_hm_micro/app/communication:sample",
528          ##start##
529          "//device/board/bearpi/bearpi_hm_micro/app/my_led_app"
530          ##end##
531
532        ],
533    }
534    ```
535
536## 三、运行结果
537
538示例代码[编译、烧录](BearPi-HM_Micro开发板编译调试.md)后,在命令行输入以下指令可控制开发板的LED灯。
539
540关闭LED:
541```
542./bin/my_led 0
543```
544开启LED:
545```
546./bin/my_led 1
547```
548翻转LED:
549```
550./bin/my_led 2
551```
552
553从以下日志的Get reply中可以收到驱动上报的当前灯的状态,"0"表示当前灯为关闭状态,"1"表示当前灯为打开状态,
554
555```
556OHOS #
557OHOS # ./bin/my_led 0
558OHOS #
559Argument 0 is bin/my_led.
560
561Argument 1 is 0.
562
563Get reply is: 0
564exit01-01 00:01:06.784 19 43 E 02500/led_driver: Led driver dispatch
565
566OHOS #
567OHOS # ./bin/my_led 1
568OHOS #
569Argument 0 is bin/my_led.
570
571Argument 1 is 1.
572
573Get reply is: 1
574exit01-01 00:01:08.833 20 43 E 02500/led_driver: Led driver dispatch
575
576OHOS #
577OHOS # ./bin/my_led 2
578OHOS #
579Argument 0 is bin/my_led.
580
581Argument 1 is 2.
582
583Get reply is: 0
584exit01-01 00:01:11.391 21 43 E 02500/led_driver: Led driver dispatch
585
586
587```
588## 总结<a name="section9712145420182"></a>
589
590用户程序是无法直接访问驱动的,当只有驱动程序向用户态暴露server后,用户程序才能通过Dispatch的方式发送指令到驱动程序,并可以将用户态的数据携带到驱动程序,也可以从驱动程序读出数据,如下图所示为用户态程序与驱动自己数据交互的过程。
591
592![](figures/发送消息到HDF.png)
593
5941. 发送LED_WRITE_READ命令到驱动,此处开发者可以自定义创建更多的命令。
5952. 携带数据到驱动,解析出开关灯的动作。
5963. 读取IO口电平状态并通过reply携带到用户程序。
597
598在此希望开发者能仔细琢磨并掌握整个流程,掌握用户态应用程序与内核态驱动程序之间的数据交互流程,以及驱动的编写方式。
599