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