• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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```