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![GPIO统一服务模式结构图](figures/统一服务模式结构图.png)
48
49## 使用指导
50
51### 场景介绍
52
53GPIO主要是对GPIO管脚资源进行管理。开发者可以使用提供的GPIO操作接口,实现对管脚控制的具体控制。
54
55### 接口说明
56
57GPIO模块提供的主要接口如表1所示。具体API详见//drivers/hdf_core/framework/include/platform/gpio_if.h58
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>![](../public_sys-resources/icon-note.gif) **说明:**<br>
74>本文涉及GPIO的所有接口,支持内核态及用户态使用。
75
76### 开发步骤
77
78GPIO标准API通过GPIO管脚号来操作指定管脚,使用GPIO的一般流程如图2所示。
79
80**图 2** GPIO使用流程图
81
82![GPIO使用流程图](figures/GPIO使用流程图.png)
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> ![icon-caution.gif](public_sys-resources/icon-caution.gif) **注意:**<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> ![icon-caution.gif](public_sys-resources/icon-caution.gif) **注意:**<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