driver-platform-gpio-des.md 9.7 KB
Newer Older
D
duangavin123 已提交
1
# GPIO
D
duangavin123 已提交
2 3


D
duangavin123 已提交
4
## 概述
D
duangavin123 已提交
5 6 7 8 9

GPIO(General-purpose input/output)即通用型输入输出。通常,GPIO控制器通过分组的方式管理所有GPIO管脚,每组GPIO有一个或多个寄存器与之关联,通过读写寄存器完成对GPIO管脚的操作。

GPIO接口定义了操作GPIO管脚的标准方法集合,包括:

D
duangavin123 已提交
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
- 设置管脚方向:方向可以是输入或者输出(暂不支持高阻态)

- 读写管脚电平值:电平值可以是低电平或高电平

- 设置管脚中断服务函数:设置一个管脚的中断响应函数,以及中断触发方式

- 使能和禁止管脚中断:禁止或使能管脚中断


## 接口说明

  **表1** GPIO驱动API接口功能介绍

| 功能分类 | 接口描述 | 
| -------- | -------- |
| GPIO读写 | -&nbsp;GpioRead:读管脚电平值<br/>-&nbsp;GpioWrite:写管脚电平值 | 
| GPIO配置 | -&nbsp;GpioSetDir:设置管脚方向<br/>-&nbsp;GpioGetDir:获取管脚方向 | 
| GPIO中断设置 | -&nbsp;GpioSetIrq:设置管脚对应的中断服务函数<br/>-&nbsp;GpioUnSetIrq:取消管脚对应的中断服务函数<br/>-&nbsp;GpioEnableIrq:使能管脚中断<br/>-&nbsp;GpioDisableIrq:禁止管脚中断 | 

> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> 本文涉及的所有接口,仅限内核态使用,不支持在用户态使用。


## 使用指导


### 使用流程

GPIO标准API通过GPIO管脚号来操作指定管脚,使用GPIO的一般流程如下图所示。

  **图1** GPIO使用流程图

42
  ![image](figures/GPIO使用流程图.png "GPIO使用流程图")
D
duangavin123 已提交
43 44 45


### 确定GPIO管脚号
W
wenjun 已提交
46 47 48

不同SOC芯片由于其GPIO控制器型号、参数、以及控制器驱动的不同,GPIO管脚号的换算方式不一样。

D
duangavin123 已提交
49 50
- Hi3516DV300
  控制器管理12组GPIO管脚,每组8个。
W
wenjun 已提交
51

D
duangavin123 已提交
52
  GPIO号 = GPIO组索引 (0~11) \* 每组GPIO管脚数(8) + 组内偏移
W
wenjun 已提交
53

D
duangavin123 已提交
54
  举例:GPIO10_3的GPIO号 = 10 \* 8 + 3 = 83
D
duangavin123 已提交
55

D
duangavin123 已提交
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
- Hi3518EV300
  控制器管理10组GPIO管脚,每组10个。

  GPIO号 = GPIO组索引 (0~9) \* 每组GPIO管脚数(10) + 组内偏移

  举例:GPIO7_3的GPIO管脚号 = 7 \* 10 + 3 = 73


### 使用API操作GPIO管脚

- 设置GPIO管脚方向
  在进行GPIO管脚读写前,需要先通过如下函数设置GPIO管脚方向:

  int32_t GpioSetDir(uint16_t gpio, uint16_t dir);

    **表2** GpioSetDir参数和返回值描述
  
  | **参数**| **参数描述** |
  | -------- | -------- |
  | gpio | 待设置的GPIO管脚号 | 
  | dir | 待设置的方向值 | 
  | **返回值** | **返回值描述** | 
  | 0 | 设置成功 | 
  | 负数 | 设置失败 | 

- 读写GPIO管脚
D
duangavin123 已提交
82

D
duangavin123 已提交
83
  如果要读取一个GPIO管脚电平,通过以下函数完成:
D
duangavin123 已提交
84

D
duangavin123 已提交
85
  int32_t GpioRead(uint16_t gpio, uint16_t \*val);
D
duangavin123 已提交
86

D
duangavin123 已提交
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
    **表3** GpioRead参数和返回值描述
  
  | **参数** | **参数描述** | 
  | -------- | -------- | 
  | gpio | 待读取的GPIO管脚号 | 
  | val | 接收读取电平值的指针 | 
  | **返回值** | **返回值描述** | 
  | 0 | 读取成功 | 
  | 负数 | 读取失败 | 

  如果要向GPIO管脚写入电平值,通过以下函数完成:

  int32_t GpioWrite(uint16_t gpio, uint16_t val);

    **表4** GpioWrite参数和返回值描述
  
  | **参数** | **参数描述** | 
  | -------- | -------- |
  | gpio | 待写入的GPIO管脚号 | 
  | val | 待写入的电平值 | 
  | **返回值** | **返回值描述** | 
  | 0 | 写入成功 | 
  | 负数 | 写入失败 | 

  示例代码:

    
  ```
  int32_t ret;
  uint16_t val;
  /* 将3号GPIO管脚配置为输出 */
  ret = GpioSetDir(3, GPIO_DIR_OUT);
  if (ret != 0) {
      HDF_LOGE("GpioSerDir: failed, ret %d\n", ret);
      return;
  }
  /* 向3号GPIO管脚写入低电平GPIO_VAL_LOW */
  ret = GpioWrite(3, GPIO_VAL_LOW);
  if (ret != 0) {
      HDF_LOGE("GpioWrite: failed, ret %d\n", ret);
      return;
  }
  /* 将6号GPIO管脚配置为输入 */
  ret = GpioSetDir(6, GPIO_DIR_IN);
  if (ret != 0) {
      HDF_LOGE("GpioSetDir: failed, ret %d\n", ret);
      return;
  }
  /* 读取6号GPIO管脚的电平值 */
  ret = GpioRead(6, &val);
  ```

- 设置GPIO中断

  如果要为一个GPIO管脚设置中断响应程序,使用如下函数:

  int32_t GpioSetIrq(uint16_t gpio, uint16_t mode, GpioIrqFunc func, void \*arg);

    **表5** GpioSetIrq参数和返回值描述
  
  | **参数** | **参数描述** | 
  | -------- | -------- |
  | gpio | GPIO管脚号 | 
  | mode | 中断触发模式 | 
  | func | 中断服务程序 | 
  | arg | 传递给中断服务程序的入参 | 
  | **返回值** | **返回值描述** | 
  | 0 | 设置成功 | 
  | 负数 | 设置失败 | 

  > ![icon-caution.gif](public_sys-resources/icon-caution.gif) **注意:**
  > 同一时间,只能为某个GPIO管脚设置一个中断服务函数,如果重复调用GpioSetIrq函数,则之前设置的中断服务函数会被取代。

  当不再需要响应中断服务函数时,使用如下函数取消中断设置:

  int32_t GpioUnSetIrq(uint16_t gpio);

    **表6** GpioUnSetIrq参数和返回值描述
  
  | **参数** | **参数描述** | 
  | -------- | -------- |
  | gpio | GPIO管脚号 | 
  | **返回值** | **返回值描述** | 
  | 0 | 取消成功 | 
  | 负数 | 取消失败 | 

  在中断服务程序设置完成后,还需要先通过如下函数使能GPIO管脚的中断:

  int32_t GpioEnableIrq(uint16_t gpio);

    **表7** GpioEnableIrq参数和返回值描述
  
  | **参数** | **参数描述** | 
  | -------- | -------- |
  | gpio | GPIO管脚号 | 
  | **返回值** | **返回值描述** | 
  | 0 | 使能成功 | 
  | 负数 | 使能失败 | 

  > ![icon-caution.gif](public_sys-resources/icon-caution.gif) **注意:**
  > 必须通过此函数使能管脚中断,之前设置的中断服务函数才能被正确响应。

  如果要临时屏蔽此中断,可以通过如下函数禁止GPIO管脚中断:

  int32_t GpioDisableIrq(uint16_t gpio);

    **表8** GpioDisableIrq参数和返回值描述
  
  | **参数** | **参数描述** | 
  | -------- | -------- |
  | gpio | GPIO管脚号 | 
  | **返回值** | **返回值描述** | 
  | 0 | 禁止成功 | 
  | 负数 | 禁止失败 | 

  示例代码:

    
  ```
  /* 中断服务函数*/
  int32_t MyCallBackFunc(uint16_t gpio, void *data)
  {
      HDF_LOGI("%s: gpio:%u interrupt service in! data=%p\n", __func__, gpio, data);
      return 0;
  }
  
  int32_t ret;
  /* 设置中断服务程序为MyCallBackFunc,入参为NULL,中断触发模式为上升沿触发 */
  ret = GpioSetIrq(3, OSAL_IRQF_TRIGGER_RISING, MyCallBackFunc, NULL);
  if (ret != 0) {
      HDF_LOGE("GpioSetIrq: failed, ret %d\n", ret);
      return;
  }
  
  /* 使能3号GPIO管脚中断 */
  ret = GpioEnableIrq(3);
  if (ret != 0) {
      HDF_LOGE("GpioEnableIrq: failed, ret %d\n", ret);
      return;
  }
  
  /* 禁止3号GPIO管脚中断 */
  ret = GpioDisableIrq(3);
  if (ret != 0) {
      HDF_LOGE("GpioDisableIrq: failed, ret %d\n", ret);
      return;
  }
  
  /* 取消3号GPIO管脚中断服务程序 */
  ret = GpioUnSetIrq(3);
  if (ret != 0) {
      HDF_LOGE("GpioUnSetIrq: failed, ret %d\n", ret);
      return;
  }
  ```


## 使用实例

本实例程序中,我们将测试一个GPIO管脚的中断触发:为管脚设置中断服务函数,触发方式为边沿触发,然后通过交替写高低电平到管脚,产生电平波动,制造触发条件,观察中断服务函数的执行。

首先需要选取一个空闲的GPIO管脚,本例程基于Hi3516DV300某开发板,GPIO管脚选择GPIO10_3,换算成GPIO号为83。

  读者可以根据自己使用的开发板,参考其原理图,选择一个空闲的GPIO管脚即可。
  
D
duangavin123 已提交
252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321
```
#include "gpio_if.h"
#include "hdf_log.h"
#include "osal_irq.h"
#include "osal_time.h"

static uint32_t g_irqCnt;

/* 中断服务函数*/
static int32_t TestCaseGpioIrqHandler(uint16_t gpio, void *data)
{
    HDF_LOGE("%s: irq triggered! on gpio:%u, data=%p", __func__, gpio, data);
    g_irqCnt++; /* 如果中断服务函数触发执行,则将全局中断计数加1 */
    return GpioDisableIrq(gpio);
}

/* 测试用例函数 */
static int32_t TestCaseGpioIrqEdge(void)
{
    int32_t ret;
    uint16_t valRead;
    uint16_t mode;
    uint16_t gpio = 83; /* 待测试的GPIO管脚号 */
    uint32_t timeout;

    /* 将管脚方向设置为输出 */
    ret = GpioSetDir(gpio, GPIO_DIR_OUT);
    if (ret != HDF_SUCCESS) {
        HDF_LOGE("%s: set dir fail! ret:%d\n", __func__, ret);
        return ret;
    }

    /* 先禁止该管脚中断 */
    ret = GpioDisableIrq(gpio);
    if (ret != HDF_SUCCESS) {
        HDF_LOGE("%s: disable irq fail! ret:%d\n", __func__, ret);
        return ret;
    }

    /* 为管脚设置中断服务函数,触发模式为上升沿和下降沿共同触发 */
    mode = OSAL_IRQF_TRIGGER_RISING | OSAL_IRQF_TRIGGER_FALLING;
    HDF_LOGE("%s: mode:%0x\n", __func__, mode);
    ret = GpioSetIrq(gpio, mode, TestCaseGpioIrqHandler, NULL);
    if (ret != HDF_SUCCESS) {
        HDF_LOGE("%s: set irq fail! ret:%d\n", __func__, ret);
        return ret;
    }

    /* 使能此管脚中断 */
    ret = GpioEnableIrq(gpio);
    if (ret != HDF_SUCCESS) {
        HDF_LOGE("%s: enable irq fail! ret:%d\n", __func__, ret);
        (void)GpioUnSetIrq(gpio);
        return ret;
    }

    g_irqCnt = 0; /* 清除全局计数器 */
    timeout = 0;  /* 等待时间清零 */
    /* 等待此管脚中断服务函数触发,等待超时时间为1000毫秒 */
    while (g_irqCnt <= 0 && timeout < 1000) {
        (void)GpioRead(gpio, &valRead);
        (void)GpioWrite(gpio, (valRead == GPIO_VAL_LOW) ? GPIO_VAL_HIGH : GPIO_VAL_LOW);
        HDF_LOGE("%s: wait irq timeout:%u\n", __func__, timeout);
        OsalMDelay(200); /* wait for irq trigger */
        timeout += 200;
    }
    (void)GpioUnSetIrq(gpio);
    return (g_irqCnt > 0) ? HDF_SUCCESS : HDF_FAILURE;
}
```