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.hcs。 55 ``` 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 243 在led/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 255 在led/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 381 在my_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