1# GPIO 2 3## 概述 4 5### 功能简介 6 7GPIO(General-purpose input/output)即通用型输入输出。通常,GPIO控制器通过分组的方式管理所有GPIO管脚,每组GPIO有一个或多个寄存器与之关联,通过读写寄存器完成对GPIO管脚的操作。 8 9GPIO接口定义了操作GPIO管脚的标准方法集合,包括: 10 11- 设置、获取管脚方向:方向可以是输入或者输出(暂不支持高阻态)。 12 13- 读、写管脚电平值:电平值可以是低电平或高电平。 14 15- 设置、取消管脚中断服务函数:设置一个管脚的中断响应函数,以及中断触发方式。取消一个管脚的中断服务函数。 16 17- 使能、禁止管脚中断:禁止或使能管脚中断。 18 19### 基本概念 20 21GPIO又俗称为I/O口,I指的是输入(in),O指的是输出(out)。可以通过软件来控制其输入和输出,即I/O控制。 22 23- GPIO输入 24 25 输入是检测各个引脚上的电平状态,高电平或者低电平状态。常见的输入模式有:模拟输入、浮空输入、上拉输入、下拉输入。 26 27- GPIO输出 28 29 输出是当需要控制引脚电平的高低时需要用到输出功能。常见的输出模式有:开漏输出、推挽输出、复用开漏输出、复用推挽输出。 30 31### 运作机制 32 33在HDF框架中,同类型设备对象较多时(可能同时存在十几个同类型配置器),若采用独立服务模式,则需要配置更多的设备节点,且相关服务会占据更多的内存资源。相反,采用统一服务模式可以使用一个设备服务作为管理器,统一处理所有同类型对象的外部访问(这会在配置文件中有所体现),实现便捷管理和节约资源的目的。GPIO模块接口适配模式采用统一服务模式(如图1所示)。 34 35在统一模式下,所有的控制器都被核心层统一管理,并由核心层统一发布一个服务供接口层,因此这种模式下驱动无需再为每个控制器发布服务。 36 37GPIO模块各分层作用: 38 39- 接口层提供操作GPIO管脚的标准方法。 40 41- 核心层主要提供GPIO管脚资源匹配,GPIO管脚控制器的添加、移除以及管理的能力,通过钩子函数与适配层交互,供芯片厂家快速接入HDF框架。 42 43- 适配层主要是将钩子函数的功能实例化,实现具体的功能。 44 45**图 1** GPIO统一服务模式结构图 46 47 48 49## 使用指导 50 51### 场景介绍 52 53GPIO主要是对GPIO管脚资源进行管理。开发者可以使用提供的GPIO操作接口,实现对管脚控制的具体控制。 54 55### 接口说明 56 57GPIO模块提供的主要接口如表1所示。具体API详见//drivers/hdf_core/framework/include/platform/gpio_if.h。 58 59**表 1** GPIO驱动API接口功能介绍 60 61| 接口名 | 描述 | 62| ------------------------------------------------------------ | ------------------------------ | 63| GpioGetByName(const char \*gpioName) | 获取GPIO管脚ID | 64| int32_t GpioRead(uint16_t gpio, uint16_t \*val) | 读GPIO管脚电平值 | 65| int32_t GpioWrite(uint16_t gpio, uint16_t val) | 写GPIO管脚电平值 | 66| int32_t GpioGetDir(uint16_t gpio, uint16_t \*dir) | 获取GPIO管脚方向 | 67| int32_t GpioSetDir(uint16_t gpio, uint16_t dir) | 设置GPIO管脚方向 | 68| int32_t GpioUnsetIrq(uint16_t gpio, void \*arg) | 取消GPIO管脚对应的中断服务函数 | 69| int32_t GpioSetIrq(uint16_t gpio, uint16_t mode, GpioIrqFunc func, void \*arg) | 设置GPIO管脚对应的中断服务函数 | 70| int32_t GpioEnableIrq(uint16_t gpio) | 使能GPIO管脚中断 | 71| int32_t GpioDisableIrq(uint16_t gpio) | 禁止GPIO管脚中断 | 72 73> **说明:**<br> 74>本文涉及GPIO的所有接口,支持内核态及用户态使用。 75 76### 开发步骤 77 78GPIO标准API通过GPIO管脚号来操作指定管脚,使用GPIO的一般流程如图2所示。 79 80**图 2** GPIO使用流程图 81 82 83 84#### 确定GPIO管脚号 85 86两种方式获取管脚号:根据SOC芯片规则进行计算、通过管脚别名获取 87 88- 根据SOC芯片规则进行计算 89 90 不同SOC芯片由于其GPIO控制器型号、参数、以及控制器驱动的不同,GPIO管脚号的换算方式不一样。 91 92 - Hi3516DV300 93 94 控制器管理12组GPIO管脚,每组8个。 95 96 GPIO号 = GPIO组索引 (0~11) \* 每组GPIO管脚数(8) + 组内偏移 97 98 举例:GPIO10_3的GPIO号 = 10 \* 8 + 3 = 83 99 100 - Hi3518EV300 101 102 控制器管理10组GPIO管脚,每组10个。 103 104 GPIO号 = GPIO组索引 (0~9) \* 每组GPIO管脚数(10) + 组内偏移 105 106 举例:GPIO7_3的GPIO管脚号 = 7 \* 10 + 3 = 73 107 108- 通过管脚别名获取 109 110 调用接口GpioGetByName进行获取,入参是该管脚的别名,接口返回值是管脚的全局ID。 111 112 ```c 113 GpioGetByName(const char *gpioName); 114 ``` 115 116#### 设置GPIO管脚方向 117 118在进行GPIO管脚读写前,需要先通过如下函数设置GPIO管脚方向: 119 120```c 121int32_t GpioSetDir(uint16_t gpio, uint16_t dir); 122``` 123 124**表 2** GpioSetDir参数和返回值描述 125 126| **参数** | **参数描述** | 127| ---------- | ------------------ | 128| gpio | uint16_t类型,GPIO管脚号 | 129| dir | uint16_t类型,待设置的方向值 | 130| **返回值** | **返回值描述** | 131| HDF_SUCCESS | 设置GPIO管脚方向成功 | 132| 负数 | 设置GPIO管脚方向失败 | 133 134假设需要将GPIO管脚3的方向配置为输出,其使用示例如下: 135 136```c 137int32_t ret; 138 139ret = GpioSetDir(3, GPIO_DIR_OUT); // 将3号GPIO管脚配置为输出 140if (ret != HDF_SUCCESS) { 141 HDF_LOGE("GpioSetDir: gpio set dir fail, ret:%d\n", ret); 142 return ret; 143} 144``` 145 146#### 获取GPIO管脚方向 147 148可以通过如下函数获取GPIO管脚方向: 149 150```c 151int32_t GpioGetDir(uint16_t gpio, uint16_t *dir); 152``` 153 154**表 3** GpioGetDir参数和返回值描述 155 156| **参数** | **参数描述** | 157| ---------- | ------------------ | 158| gpio | uint16_t类型,GPIO管脚号 | 159| dir | uint16_t类型指针,获取到的方向值 | 160| **返回值** | **返回值描述** | 161| HDF_SUCCESS | 获取GPIO管脚方向成功 | 162| 负数 | 获取GPIO管脚方向失败 | 163 164假设需要获取GPIO管脚3的方向,其使用示例如下: 165 166```c 167int32_t ret; 168uin16_t dir; 169 170ret = GpioGetDir(3, &dir); // 获取3号GPIO管脚方向 171if (ret != HDF_SUCCESS) { 172 HDF_LOGE("GpioGetDir: gpio get dir fail, ret:%d\n", ret); 173 return ret; 174} 175``` 176 177#### 读取GPIO管脚电平值 178 179如果要读取一个GPIO管脚电平,通过以下函数完成: 180 181```c 182int32_t GpioRead(uint16_t gpio, uint16_t *val); 183``` 184 185**表 4** GpioRead参数和返回值描述 186 187| **参数** | **参数描述** | 188| ---------- | -------------------- | 189| gpio | uint16_t类型,GPIO管脚号 | 190| val | uint16_t类型指针,接收读取电平值 | 191| **返回值** | **返回值描述** | 192| HDF_SUCCESS | 读取GPIO管脚电平值成功 | 193| 负数 | 读取GPIO管脚电平值失败 | 194 195假设需要读取GPIO管脚3的电平值,其使用示例如下: 196 197```c 198int32_t ret; 199uint16_t val; 200 201ret = GpioRead(3, &val); // 读取3号GPIO管脚电平值 202if (ret != HDF_SUCCESS) { 203 HDF_LOGE("GpioRead: gpio read fail, ret:%d\n", ret); 204 return ret; 205} 206``` 207 208#### 写入GPIO管脚电平值 209 210如果要向GPIO管脚写入电平值,通过以下函数完成: 211 212```c 213int32_t GpioWrite(uint16_t gpio, uint16_t val); 214``` 215 216**表 5** GpioWrite参数和返回值描述 217 218| **参数** | **参数描述** | 219| ---------- | ------------------ | 220| gpio | uint16_t类型,GPIO管脚号 | 221| val | uint16_t类型,待写入的电平值 | 222| **返回值** | **返回值描述** | 223| HDF_SUCCESS | 写入GPIO管脚电平值成功 | 224| 负数 | 写入GPIO管脚电平值失败 | 225 226假设需要给GPIO管脚3写入低电平值,其使用示例如下: 227 228```c 229int32_t ret; 230 231ret = GpioWrite(3, GPIO_VAL_LOW); // 给3号GPIO管脚写入低电平值 232if (ret != HDF_SUCCESS) { 233 HDF_LOGE("GpioWrite: gpio write fail, ret:%d\n", ret); 234 return ret; 235} 236``` 237 238#### 设置GPIO管脚中断 239 240如果要为一个GPIO管脚设置中断响应程序,使用如下函数: 241 242```c 243int32_t GpioSetIrq(uint16_t gpio, uint16_t mode, GpioIrqFunc func, void *arg); 244``` 245 246**表 6** GpioSetIrq参数和返回值描述 247 248| **参数** | **参数描述** | 249| ---------- | ------------------------ | 250| gpio | uint16_t类型,GPIO管脚号 | 251| mode | uint16_t类型,中断触发模式 | 252| func | 函数指针,中断服务程序 | 253| arg | 无类型指针,传递给中断服务程序的入参 | 254| **返回值** | **返回值描述** | 255| HDF_SUCCESS | 设置GPIO管脚中断成功 | 256| 负数 | 设置GPIO管脚中断失败 | 257 258>  **注意:**<br> 259> 同一时间,只能为某个GPIO管脚设置一个中断服务函数,如果重复调用GpioSetIrq函数,则之前设置的中断服务函数会被取代。 260 261#### 取消GPIO管脚中断 262 263当不再需要响应中断服务函数时,使用如下函数取消中断设置: 264 265```c 266int32_t GpioUnsetIrq(uint16_t gpio, void *arg); 267``` 268 269**表 7** GpioUnsetIrq参数和返回值描述 270 271| **参数** | **参数描述** | 272| ---------- | -------------- | 273| gpio | uint16_t类型,GPIO管脚号 | 274| arg | 无类型指针,GPIO中断数据 | 275| **返回值** | **返回值描述** | 276| HDF_SUCCESS | 取消GPIO管脚中断成功 | 277| 负数 | 取消GPIO管脚中断失败 | 278 279#### 使能GPIO管脚中断 280 281在中断服务程序设置完成后,还需要先通过如下函数使能GPIO管脚的中断: 282 283```c 284int32_t GpioEnableIrq(uint16_t gpio); 285``` 286 287**表 8** GpioEnableIrq参数和返回值描述 288 289| **参数** | **参数描述** | 290| ---------- | -------------- | 291| gpio | uint16_t类型,GPIO管脚号 | 292| **返回值** | **返回值描述** | 293| HDF_SUCCESS | 使能GPIO管脚中断成功 | 294| 负数 | 使能GPIO管脚中断失败 | 295 296>  **注意:**<br> 297> 必须通过此函数使能管脚中断,之前设置的中断服务函数才能被正确响应。 298 299#### 禁止GPIO管脚中断 300 301如果要临时屏蔽此中断,可以通过如下函数禁止GPIO管脚中断: 302 303```c 304int32_t GpioDisableIrq(uint16_t gpio); 305``` 306**表 9** GpioDisableIrq参数和返回值描述 307 308| **参数** | **参数描述**| 309| ---------- | -------------- | 310| gpio | uint16_t类型,GPIO管脚号 | 311| **返回值** | **返回值描述** | 312| HDF_SUCCESS | 禁止GPIO管脚中断成功 | 313| 负数 | 禁止GPIO管脚中断失败 | 314 315中断相关操作示例: 316 317```c 318// 中断服务函数 319int32_t MyCallBackFunc(uint16_t gpio, void *data) 320{ 321 HDF_LOGI("MyCallBackFunc: gpio:%u interrupt service in data.\n", gpio); 322 return HDF_SUCCESS; 323} 324 325int32_t ret; 326// 设置中断服务程序为MyCallBackFunc,入参为NULL,中断触发模式为上升沿触发 327ret = GpioSetIrq(3, OSAL_IRQF_TRIGGER_RISING, MyCallBackFunc, NULL); 328if (ret != HDF_SUCCESS) { 329 HDF_LOGE("GpioSetIrq: gpio set irq fail, ret:%d\n", ret); 330 return ret; 331} 332 333// 使能3号GPIO管脚中断 334ret = GpioEnableIrq(3); 335if (ret != HDF_SUCCESS) { 336 HDF_LOGE("GpioEnableIrq: gpio enable irq fail, ret:%d\n", ret); 337 return ret; 338} 339 340// 禁止3号GPIO管脚中断 341ret = GpioDisableIrq(3); 342if (ret != HDF_SUCCESS) { 343 HDF_LOGE("GpioDisableIrq: gpio disable irqfail, ret:%d\n", ret); 344 return ret; 345} 346 347// 取消3号GPIO管脚中断服务程序 348ret = GpioUnsetIrq(3, NULL); 349if (ret != HDF_SUCCESS) { 350 HDF_LOGE("GpioUnSetIrq: gpio unset irq fail, ret:%d\n", ret); 351 return ret; 352} 353``` 354 355## 使用实例 356 357本实例程序中,我们将测试一个GPIO管脚的中断触发:为管脚设置中断服务函数,触发方式为边沿触发,然后通过交替写高低电平到管脚,产生电平波动,制造触发条件,观察中断服务函数的执行。 358 359首先需要选取一个空闲的GPIO管脚,本例程基于Hi3516DV300开发板,GPIO管脚选择GPIO10_3,换算成GPIO号为83。 360 361读者可以根据自己使用的开发板,参考其原理图,选择一个空闲的GPIO管脚即可。 362 363```c 364#include "gpio_if.h" 365#include "hdf_log.h" 366#include "osal_irq.h" 367#include "osal_time.h" 368 369static uint32_t g_irqCnt; 370 371// 中断服务函数 372static int32_t TestCaseGpioIrqHandler(uint16_t gpio, void *data) 373{ 374 HDF_LOGE("TestCaseGpioIrqHandler: irq triggered! on gpio:%u, in data", gpio); 375 g_irqCnt++; // 如果中断服务函数触发执行,则将全局中断计数加1 376 return GpioDisableIrq(gpio); 377} 378 379// 测试用例函数 380static int32_t TestCaseGpioIrqEdge(void) 381{ 382 int32_t ret; 383 uint16_t valRead; 384 uint16_t mode; 385 uint16_t gpio = 84; // 待测试的GPIO管脚号 386 uint32_t timeout; 387 388 // 将管脚方向设置为输出 389 ret = GpioSetDir(gpio, GPIO_DIR_OUT); 390 if (ret != HDF_SUCCESS) { 391 HDF_LOGE("TestCaseGpioIrqEdge: set dir fail! ret:%d\n", ret); 392 return ret; 393 } 394 395 // 先禁止该管脚中断 396 ret = GpioDisableIrq(gpio); 397 if (ret != HDF_SUCCESS) { 398 HDF_LOGE("TestCaseGpioIrqEdge: disable irq fail! ret:%d\n", ret); 399 return ret; 400 } 401 402 // 为管脚设置中断服务函数,触发模式为上升沿和下降沿共同触发 403 mode = OSAL_IRQF_TRIGGER_RISING | OSAL_IRQF_TRIGGER_FALLING; 404 HDF_LOGE("TestCaseGpioIrqEdge: mode:%0x\n", mode); 405 ret = GpioSetIrq(gpio, mode, TestCaseGpioIrqHandler, NULL); 406 if (ret != HDF_SUCCESS) { 407 HDF_LOGE("TestCaseGpioIrqEdge: set irq fail! ret:%d\n", ret); 408 return ret; 409 } 410 411 // 使能此管脚中断 412 ret = GpioEnableIrq(gpio); 413 if (ret != HDF_SUCCESS) { 414 HDF_LOGE("TestCaseGpioIrqEdge: enable irq fail! ret:%d\n", ret); 415 (void)GpioUnsetIrq(gpio, NULL); 416 return ret; 417 } 418 419 g_irqCnt = 0; // 清除全局计数器 420 timeout = 0; // 等待时间清零 421 // 等待此管脚中断服务函数触发,等待超时时间为1000毫秒 422 while (g_irqCnt <= 0 && timeout < 1000) { 423 (void)GpioRead(gpio, &valRead); 424 (void)GpioWrite(gpio, (valRead == GPIO_VAL_LOW) ? GPIO_VAL_HIGH : GPIO_VAL_LOW); 425 HDF_LOGE("TestCaseGpioIrqEdge: wait irq timeout:%u\n", timeout); 426 OsalMDelay(200); // 等待中断触发 427 timeout += 200; 428 } 429 (void)GpioUnsetIrq(gpio, NULL); 430 HDF_LOGI("TestCaseGpioIrqEdge: function tests end, g_irqCnt:%u", g_irqCnt); 431 return (g_irqCnt > 0) ? HDF_SUCCESS : HDF_FAILURE; 432} 433``` 434