# MS-RTOS GPIO 驱动开发

本章将介绍 MS-RTOS GPIO 驱动开发及测试。

GPIO 是通用输入/输出端口(General Purpose I/O Ports)的英文缩写,通俗地说,就是一些引脚,可以通过它们输出高低电平或者通过它们读入引脚的状态:是高电平或是低电平。最基本的输出功能是由引脚输出高、低电平,实现开关控制,如把GPIO引脚接入LED灯,那就可以控制LED灯的亮灭,引脚接入到继电器或三极管,那就可以通过继电器或三极管控制外部大功率电路的通断。 最基本的输入功能是检测外部电平,如把GPIO引脚连接到按键,通过电平高低区分按键是否被按下。

# 1. GPIO 基础知识

# 1.1 硬件结构

通过 GPIO 硬件结构框图,就可以从整体上深入了解 GPIO 外设及它的各种应用模式,该图从最右端看起,最右端就是代表 STM32 芯片引出的 GPIO 引脚,其它部件都位于芯片内部。

gpio_arch

推挽输出模式一般应用在输出电平0和3.3伏而且需要高速切换开关状态的场合。在STM32的应用中,除了必须用开漏模式的场合,我们都习惯使用推挽输出模式。开漏输出一般应用在I2C、SMBUS通讯等需要“线与”功能的总线电路中。除此之外,还用在电平不匹配的场合,如需要输出5伏的高电平,就可以在外部接一个上拉电阻,上拉电源为5伏,并且把GPIO设置为开漏模式,当输出高阻态时,由上拉电阻和电源向外输出5伏电平。可以输出高低电平,用于连接数字器件,高电平由VDD决定,低电平由VSS决定。推挽结构指两个三极管受两路互补的信号控制,总是在一个导通的时候另外一个截止,优点开关效率高,电流大,驱动能力强。输出高电平时,电流输出到负载,叫灌电流,可以理解成推,输出低电平时,负载电流流向芯片,叫拉电流,即挽。

# 1.2 工作模式

输入模式:

  • 输入浮空(GPIO_Mode_IN_FLOATING):

    通俗讲浮空就是浮在空中,就相当于此端口在默认情况下什么都不接,呈高阻态,这种设置在数据传输时用的比较多。浮空最大的特点就是电压的不确定性,它可能是0V,页可能是VCC,还可能是介于两者之间的某个值(最有可能) 浮空一般用来做ADC输入用,这样可以减少上下拉电阻对结果的影响。

  • 输入上拉(GPIO_Mode_IPU):

    上拉就是将不确定的信号通过一个电阻嵌位在高电平。电阻同时起到限流的作用。弱强只是上拉电阻的阻值不同,没有什么严格区分。

  • 输入下拉(GPIO_Mode_IPD):

    就是把电压拉低,拉到GND。与上拉原理相似。

  • 模拟输入(GPIO_Mode_AIN):

    模拟输入是指传统方式的输入,数字输入是输入PCM数字信号,即0、1的二进制数字信号,通过数模转换,

    转换成模拟信号,经前级放大进入功率放大器,功率放大器还是模拟的。

输出模式:

  • 开漏输出(GPIO_Mode_Out_OD)

    这里要注意N-MOS管,当设置输出的值为高电平的时候,N-MOS管处于关闭状态,此时I/O端口的电平就不会由输出的高低电平决定,而是由I/O端口外部的上拉或者下拉决定;当设置输出的值为低电平的时候,N-MOS管处于开启状态,此时I/O端口的电平就是低电平。同时,I/O端口的电平也可以通过输入电路进行读取;注意,I/O端口的电平不一定是输出的电平。

  • 开漏复用功能(GPIO_Mode_AF_OD)

    开漏复用输出模式,与开漏输出模式很是类似。只是输出的高低电平的来源,不是让CPU直接写输出数据寄存器,取而代之利用片上外设模块的复用功能输出来决定的。

  • 推挽式输出(GPIO_Mode_Out_PP)

    注意P-MOS管和N-MOS管,当设置输出的值为高电平的时候,P-MOS管处于开启状态,N-MOS管处于关闭状态,此时I/O端口的电平就由P-MOS管决定:高电平;当设置输出的值为低电平的时候,P-MOS管处于关闭状态,N-MOS管处于开启状态,此时I/O端口的电平就由N-MOS管决定:低电平。同时,I/O端口的电平也可以通过输入电路进行读取;注意,此时I/O端口的电平一定是输出的电平。

  • 推挽式复用功能(GPIO_Mode_AF_PP)

    推挽复用输出模式,与推挽输出模式很是类似。只是输出的高低电平的来源,不是让CPU直接写输出数据寄存器,取而代之利用片上外设模块的复用功能输出来决定的。

# 2. GPIO 驱动框架

# 2.1 驱动相关数据结构

(1)ms_io_device_t

/*
 * ms_io_device_t
 */
struct ms_io_device {
    ms_io_name_node_t       nnode;
    ms_io_driver_t         *drv;

    ms_ptr_t                ctx;
    ms_atomic_t             ref;
};

任何 IO 设备对象都应该采用包含的方式来继承 ms_io_device 对象。在实现 UART 驱动时,可以自定义一个设备结构体 uart_dev_t ,该结构体中包含一个 ms_io_device 类型的成员,还包含一个 privinfo_t 类型的成员;privinfo_t 由驱动开发人员定义,一般用于记录一些设备私有的状态信息和控制信息。

/*
 * Private Info
 */
typedef struct {
    ms_pollfd_t     *slots[1];
    ms_addr_t        base;
    ms_gpio_param_t  param;
    gpio_port_t      gpio_port;
    gpio_pin_t       gpio_pin;
} privinfo_t;

/*
 * GPIO Device
 */
typedef struct {
    privinfo_t       priv;
    ms_io_device_t   dev;
} gpio_dev_t;

(2)ms_io_driver_t

/*
 * ms_io_driver_ops_t
 */
typedef struct {
    ms_io_drv_type_t type;

    int        (*open)  (ms_ptr_t ctx, ms_io_file_t *file, int oflag, ms_mode_t mode);
    int        (*close) (ms_ptr_t ctx, ms_io_file_t *file);
    ms_ssize_t (*read)  (ms_ptr_t ctx, ms_io_file_t *file, 
                         ms_ptr_t buf, ms_size_t len);
    ms_ssize_t (*write) (ms_ptr_t ctx, ms_io_file_t *file, 
                         ms_const_ptr_t buf, ms_size_t len);
    int        (*ioctl) (ms_ptr_t ctx, ms_io_file_t *file, int cmd, ms_ptr_t arg);
} const ms_io_driver_ops_t;

/*
 * ms_io_driver_t
 */
struct ms_io_driver {
    ms_io_name_node_t      nnode;
    ms_io_driver_ops_t    *ops;
};

任何 IO 设备驱动对象都应该采用包含的方式来继承 ms_io_driver 对象。ms_io_driver 结构体包含一个名字节点 ms_io_name_node_t 和一个设备文件操作接口集 ms_io_driver_ops_t 指针;名字节点用于匹配设备,文件操作接口用于支持 MS-RTOS IO 系统对设备的访问和控制。

/*
 * Device operating function set
 */
static const ms_io_driver_ops_t gpio_drv_ops = {
        .type   = MS_IO_DRV_TYPE_CHR,
        .open   = __gpio_open,
        .close  = __gpio_close,
        .read   = __gpio_read,
        .write  = __gpio_write,
        .ioctl  = __gpio_ioctl,
        .poll   = __gpio_poll,
};

/*
 * Device driver
 */
static ms_io_driver_t ck807_gpio_drv = {
        .nnode = {
            .name = "stm32f4_gpio",
        },
        .ops = &gpio_drv_ops,
};

# 2.2 驱动的注册和卸载

(1)GPIO 驱动开发流程:

  • 获取必要的软硬件开发资源,了解设备的基本特性;

  • 参照手册的相关流程和代码规范,编写寄存器相关宏定义,封装通用硬件操作接口;

  • 申请必要的系统资源,根据默认参数初始化硬件的工作模式,实现中断处理函数;

  • 实现 ms_io_driver_ops_t 中的必要操作接口,并向 MS-RTOS IO 系统注册驱动和设备节点;

  • 检查代码质量和代码风格,编写测试程序;

(2)驱动的注册和卸载接口:

ms_err_t ms_io_driver_register(ms_io_driver_t *drv);
ms_err_t ms_io_driver_unregister(ms_io_driver_t *drv); //此接口暂不开放

(3)设备节点的注册和卸载接口:

ms_err_t ms_io_device_register(ms_io_device_t *dev, const char *dev_path, 
                               const char *drv_name, ms_ptr_t ctx);
ms_err_t ms_io_device_unregister(ms_io_device_t *dev);

# 2.3 GPIO ioctl 命令

以下仅列出几个最基本的命令,可以在 sdk/src/driver/ms_drv_gpio.h 文件中找到所有命令的定义。

命令 描述 参数
MS_GPIO_CMD_SET_VAL 设置GPIO引脚的输出值 ms_uint8_t 指针
MS_GPIO_CMD_GET_VAL 读取GPIO引脚的输入值 ms_uint8_t 指针
MS_GPIO_CMD_SET_PARAM 设置GPIO引脚的工作模式 ms_gpio_param_t 指针
MS_GPIO_CMD_GET_PARAM 获取GPIO引脚的工作模式 ms_gpio_param_t 指针

(1)ms_gpio_param_t

typedef struct {
    ms_uint8_t      mode;			// GPIO 引脚工作模式 (输入输出模式、中断触发模式)
    ms_uint8_t      pull;			// GPIO 引脚上下拉设置
    ms_uint8_t      speed;			// GPIO 引脚工作在哪个范围的频率下
} ms_gpio_param_t;
  • GPIO 引脚工作模式 (mode)
可选配置 描述
MS_GPIO_MODE_INPUT Input Mode
MS_GPIO_MODE_OUTPUT_PP Output Push Pull Mode
MS_GPIO_MODE_OUTPUT_OD Output Open Drain Mode
MS_GPIO_MODE_IRQ_RISING External Interrupt Mode with Rising edge trigger detection
MS_GPIO_MODE_IRQ_FALLING External Interrupt Mode with Falling edge trigger detection
MS_GPIO_MODE_IRQ_BOTH External Interrupt Mode with Rising/Falling edge trigger detection
MS_GPIO_MODE_IRQ_HIGH External Interrupt Mode with High level detection
MS_GPIO_MODE_IRQ_LOW External Interrupt Mode with Low level detection
  • GPIO 引脚工作模式 (pull)
可选配置 描述
MS_GPIO_PULL_NONE No Pull-up or Pull-down activation
MS_GPIO_PULL_UP Pull-up activation
MS_GPIO_PULL_DOWN Pull-down activation
  • GPIO 引脚工作模式 (speed)
可选配置 描述
MS_GPIO_SPEED_LOW IO works at 2 MHz
MS_GPIO_SPEED_MEDIUM Range 12.5 MHz to 50 MHz
MS_GPIO_SPEED_HIGH Range 25 MHz to 100 MHz
MS_GPIO_SPEED_VERY_HIGH Range 50 MHz to 200 MHz

# 2.4 GPIO 读写

应用程序在打开设备文件后,需要调用 ioctl 来获取 GPIO 的默认工作模式或设置 GPIO 的工作模式。当应用程序读取相应 GPIO 引脚的设备文件时,需要将至少一个 Byte 大小的缓冲传递给底层的驱动,驱动程序将判断当前操作的 GPIO 是否处于出入模式;如果处于输入模式则读取 GPIO 引脚的电平,并返回 GPIO 状态给应用程序 —— 0:代表低电平,1:代表高电平;如果不处于输入模式则,read 调用将返回 0 表示读取失败。

/*
 * Read device
 */
static ms_ssize_t __gpio_read(ms_ptr_t ctx, ms_io_file_t *file, ms_ptr_t buf, ms_size_t len)
{
    privinfo_t *priv = ctx;
    ms_uint8_t *value = (ms_uint8_t *)buf;

    if (__gpio_get_dir(priv) != GPIO_DIR_INPUT) {
        ms_thread_set_errno(EINVAL);
        return 0;
    }

    if (__gpio_get_val(priv) == GPIO_VALUE_LOW) {
        *value = 0;
    } else {
        *value = 1;
    }

    return 1;
}

应用程序在写入相应 GPIO 引脚的设备文件时,驱动程序将判断当前操作的 GPIO 是否处于输出模式,如果处于输出模式则将根据写入的值来设置 GPIO 引脚的电平,0:代表低电平,1:代表高电平。

/*
 * Write device
 */
static ms_ssize_t __gpio_write(ms_ptr_t ctx, ms_io_file_t *file, ms_const_ptr_t buf, ms_size_t len)
{
    privinfo_t *priv = ctx;
    ms_uint8_t *value = (ms_uint8_t *)buf;

    if (__gpio_get_dir(priv) != GPIO_DIR_OUTPUT) {
        ms_thread_set_errno(EINVAL);
        return 0;
    }

    if (value[0]) {
        __gpio_set_val(priv, GPIO_VALUE_HIGH);
    } else {
        __gpio_set_val(priv, GPIO_VALUE_LOW);
    }

    return 1;
}

# 2.5 GPIO select 支持

GPIO 设备支持输入、输出和异常事件地捕获。当监听输入输出事件时,即判断当前 GPIO 是否为输入或输出模式;当监听异常事件时,即判断当前 GPIO 是否有中断挂起。

/*
 * Check device readable
 */
static ms_bool_t __gpio_readable_check(ms_ptr_t ctx)
{
    privinfo_t *priv = ctx;

    if (__gpio_get_dir(priv) == GPIO_DIR_INPUT) {
        return MS_TRUE;
    } else {
        return MS_FALSE;
    }
}

/*
 * Check device writable
 */
static ms_bool_t __gpio_writable_check(ms_ptr_t ctx)
{
    privinfo_t *priv = ctx;

    if (__gpio_get_dir(priv) == GPIO_DIR_OUTPUT) {
        return MS_TRUE;
    } else {
        return MS_FALSE;
    }
}

/*
 * Check device exception
 */
static ms_bool_t __gpio_except_check(ms_ptr_t ctx)
{
    privinfo_t *priv = ctx;

    return __gpio_is_irq_pending(priv);
}

/*
 * Poll device
 */
static int __gpio_poll(ms_ptr_t ctx, ms_io_file_t *file, 
                       ms_pollfd_t *fds, ms_bool_t setup)
{
    privinfo_t *priv = ctx;

    return ms_io_poll_helper(fds, priv->slots, MS_ARRAY_SIZE(priv->slots), setup, ctx,
                             __gpio_readable_check, 
                             __gpio_writable_check, 
                             __gpio_except_check);
}

# 2.6 GPIO 驱动示例

GPIO 驱动示例,仅作为参考。

#define __MS_IO
#include "config.h"
#include "ms_kern.h"
#include "ms_io_core.h"

/*
 * Open device
 */
static int __gpio_open(ms_ptr_t ctx, ms_io_file_t *file, int oflag, ms_mode_t mode)
{
    int ret;

    if (ms_atomic_inc(MS_IO_DEV_REF(file)) == 1) {
        ret = 0;

    } else {
        ms_atomic_dec(MS_IO_DEV_REF(file));
        ms_thread_set_errno(EBUSY);
        ret = -1;
    }

    return ret;
}

/*
 * Close device
 */
static int __gpio_close(ms_ptr_t ctx, ms_io_file_t *file)
{
    privinfo_t *priv = ctx;
    ms_uint16_t pin = priv->pin;
    GPIO_TypeDef *gpio_x = (GPIO_TypeDef *)priv->base;

    if (ms_atomic_dec(MS_IO_DEV_REF(file)) == 0) {
        HAL_GPIO_DeInit(gpio_x, pin);
    }

    return 0;
}

/*
 * Read device
 */
static ms_ssize_t __gpio_read(ms_ptr_t ctx, ms_io_file_t *file, ms_ptr_t buf, ms_size_t len)
{
    privinfo_t *priv = ctx;
    ms_uint16_t pin = priv->pin;
    ms_uint8_t *value = (ms_uint8_t *)buf;
    GPIO_TypeDef *gpio_x = (GPIO_TypeDef *)priv->base;
    GPIO_PinState state;
    
    state = HAL_GPIO_ReadPin(gpio_x, pin);
    *value = (state == GPIO_PIN_RESET) ? 0 : 1;

    return sizeof(ms_uint8_t);
}

/*
 * Write device
 */
static ms_ssize_t __gpio_write(ms_ptr_t ctx, ms_io_file_t *file, ms_const_ptr_t buf, ms_size_t len)
{
    privinfo_t *priv = ctx;
    ms_uint16_t pin = priv->pin;
    GPIO_TypeDef *gpio_x = (GPIO_TypeDef *)priv->base;
    GPIO_PinState state;

    state = (*(ms_uint8_t *)buf) ? GPIO_PIN_SET : GPIO_PIN_RESET;
    HAL_GPIO_WritePin(gpio_x, pin, state);

    return sizeof(ms_uint8_t);
}

/*
 * GPIO pin to pos [0...15]
 */
static ms_uint8_t __gpio_pin_to_pos(ms_uint16_t pin)
{
    ms_uint8_t pos;

    for (pos = 0; pos < GPIO_NUMBER; pos++) {
        if (pin == (((ms_uint16_t)0x01) << pos)) {
            break;
        }
    }

    return pos;
}

/*
 * Covert general flags to stm32 flags
 */
static ms_err_t __gpio_covert_to_hal_flag(const ms_gpio_param_t *param, 
                                          GPIO_InitTypeDef *init)
{
    /*
     * Covert mode
     */
    switch (param->mode) {
    case MS_GPIO_MODE_INPUT:
        init->Mode = GPIO_MODE_INPUT;
        break;

    case MS_GPIO_MODE_OUTPUT_PP:
        init->Mode = GPIO_MODE_OUTPUT_PP;
        break;

    case MS_GPIO_MODE_OUTPUT_OD:
        init->Mode = GPIO_MODE_OUTPUT_OD;
        break;

    case MS_GPIO_MODE_IRQ_RISING:
        init->Mode = GPIO_MODE_IT_RISING;
        break;

    case MS_GPIO_MODE_IRQ_FALLING:
        init->Mode = GPIO_MODE_IT_FALLING;
        break;

    case MS_GPIO_MODE_IRQ_BOTH:
        init->Mode = GPIO_MODE_IT_RISING_FALLING;
        break;

    default:
        return MS_ERR;
    }

    /*
     * Covert pull
     */
    switch (param->pull) {
    case MS_GPIO_PULL_NONE:
        init->Pull = GPIO_NOPULL;
        break;

    case MS_GPIO_PULL_UP:
        init->Pull = GPIO_PULLUP;
        break;

    case MS_GPIO_PULL_DOWN:
        init->Pull = GPIO_PULLDOWN;
        break;

    default:
        return MS_ERR;
    }

    /*
     * Covert speed
     */
    switch (param->speed) {
    case MS_GPIO_SPEED_LOW:
        init->Speed = GPIO_SPEED_FREQ_LOW;
        break;

    case MS_GPIO_SPEED_MEDIUM:
        init->Speed = GPIO_SPEED_FREQ_MEDIUM;
        break;

    case MS_GPIO_SPEED_HIGH:
        init->Speed = GPIO_SPEED_FREQ_HIGH;
        break;

    case MS_GPIO_SPEED_VERY_HIGH:
        init->Speed = GPIO_SPEED_FREQ_VERY_HIGH;
        break;

    default:
        return MS_ERR;
    }

    return MS_ERR_NONE;
}

/*
 * Control device
 */
static int __gpio_ioctl(ms_ptr_t ctx, ms_io_file_t *file, int cmd, void *arg)
{
    privinfo_t *priv = ctx;
    ms_uint16_t pin = priv->pin;
    GPIO_TypeDef *gpio_x = (GPIO_TypeDef *)priv->base;
    GPIO_PinState state;
    GPIO_InitTypeDef init;
    ms_gpio_param_t *param;
    int ret;

    switch (cmd) {
    case MS_GPIO_CMD_SET_VAL:
        if (ms_access_ok(arg, sizeof(ms_uint8_t), MS_ACCESS_R)) {
            state = (*(ms_uint8_t *)arg) ? GPIO_PIN_SET : GPIO_PIN_RESET;
            HAL_GPIO_WritePin(gpio_x, pin, state);
            ret = 0;
        } else {
            ms_thread_set_errno(EFAULT);
            ret = -1;
        }
        break;

    case MS_GPIO_CMD_GET_VAL:
        if (ms_access_ok(arg, sizeof(ms_uint8_t), MS_ACCESS_W)) {
            state = HAL_GPIO_ReadPin(gpio_x, pin);
            *(ms_uint8_t *)arg = (state == GPIO_PIN_RESET) ? 0 : 1;
            ret = 0;
        } else {
            ms_thread_set_errno(EFAULT);
            ret = -1;
        }
        break;

    case MS_GPIO_CMD_SET_PARAM:
        if (ms_access_ok(arg, sizeof(ms_gpio_param_t), MS_ACCESS_R)) {
            param = (ms_gpio_param_t *)arg;
            init.Pin = pin;
            if (__gpio_covert_to_hal_flag(param, &init) == MS_ERR_NONE) {
                HAL_GPIO_Init(gpio_x, &init);

                if (IS_GPIO_EXTI_MODE(init.Mode)) {
                    gpio_exti_line_priv[__gpio_pin_to_pos(pin)] = priv;
                    HAL_NVIC_SetPriority(gpio_exti_line_irqn[__gpio_pin_to_pos(pin)],
                                         0x0F, 0x00);
                    HAL_NVIC_EnableIRQ(gpio_exti_line_irqn[__gpio_pin_to_pos(pin)]);
                }

                /*
                 * Save param in priv
                 */
                priv->mode = param->mode;
                priv->pull = param->pull;
                priv->speed = param->speed;

                ret = 0;
            } else {
                ms_thread_set_errno(EINVAL);
                ret = -1;
            }
        } else {
            ms_thread_set_errno(EFAULT);
            ret = -1;
        }
        break;

    case MS_GPIO_CMD_GET_PARAM:
        if (ms_access_ok(arg, sizeof(ms_gpio_param_t), MS_ACCESS_W)) {
            param = (ms_gpio_param_t *)arg;
            param->mode = priv->mode;
            param->pull = priv->speed;
            param->speed = priv->speed;
            ret = 0;
        } else {
            ms_thread_set_errno(EFAULT);
            ret = -1;
        }
        break;

    default:
        ms_thread_set_errno(EINVAL);
        ret = -1;
        break;
    }

    return ret;
}

/*
 * Device notify
 */
static int __gpio_poll_notify(privinfo_t *priv, ms_pollevent_t event)
{
    return ms_io_poll_notify_helper(priv->slots, MS_ARRAY_SIZE(priv->slots), event);
}

/*
 * This function called by all external interrupt handles
 */
void HAL_GPIO_EXTI_Callback(ms_uint16_t GPIO_Pin)
{
    privinfo_t *priv = gpio_exti_line_priv[__gpio_pin_to_pos(GPIO_Pin)];

    __gpio_poll_notify(priv, POLLIN);
}

/*
 * This function handles EXTI0 interrupt
 */
void EXTI0_IRQHandler(void)
{
    (void)ms_int_enter();

    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);

    (void)ms_int_exit();
}
/*
 * This function handles EXTI1 interrupt
 */
void EXTI1_IRQHandler(void)
{
    (void)ms_int_enter();

    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_1);

    (void)ms_int_exit();
}
/*
 * This function handles EXTI2 interrupt
 */
void EXTI2_IRQHandler(void)
{
    (void)ms_int_enter();

    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_2);

    (void)ms_int_exit();
}
/*
 * This function handles EXTI3 interrupt
 */
void EXTI3_IRQHandler(void)
{
    (void)ms_int_enter();

    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_3);

    (void)ms_int_exit();
}
/*
 * This function handles EXTI4 interrupt
 */
void EXTI4_IRQHandler(void)
{
    (void)ms_int_enter();

    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_4);

    (void)ms_int_exit();
}

/*
 * This function handles EXTI9-EXTI5 interrupts
 */
void EXTI9_5_IRQHandler(void)
{
    (void)ms_int_enter();

    if (__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_9) != RESET) {
        HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_9);
    }

    if (__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_8) != RESET) {
        HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_8);
    }

    if (__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_7) != RESET) {
        HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_7);
    }

    if (__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_6) != RESET) {
        HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_6);
    }

    if (__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_5) != RESET) {
        HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_5);
    }

    (void)ms_int_exit();
}

/*
 * This function handles EXTI15-EXTI10 interrupts
 */
void EXTI15_10_IRQHandler(void)
{
    (void)ms_int_enter();

    if (__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_15) != RESET) {
        HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_15);
    }

    if (__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_14) != RESET) {
        HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_14);
    }

    if (__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_13) != RESET) {
        HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13);
    }

    if (__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_12) != RESET) {
        HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_12);
    }

    if (__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_11) != RESET) {
        HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_11);
    }

    if (__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_10) != RESET) {
        HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_10);
    }

    (void)ms_int_exit();
}

/*
 * Check device readable 
 */
static ms_bool_t __gpio_readable_check(ms_ptr_t ctx)
{
    privinfo_t *priv = ctx;
    ms_bool_t ret;

    /*
     * Check EXTI line pending bit
     */
    if (__HAL_GPIO_EXTI_GET_IT(priv->pin) != RESET) {
        ret = MS_TRUE;
    } else {
        ret = MS_FALSE;
    }

    return ret;
}

/*
 * Poll device
 */
static int __gpio_poll(ms_ptr_t ctx, ms_io_file_t *file, 
                       ms_pollfd_t *fds, ms_bool_t setup)
{
    privinfo_t *priv = ctx;

    return ms_io_poll_helper(fds, priv->slots, MS_ARRAY_SIZE(priv->slots), setup, ctx,
                             __gpio_readable_check, MS_NULL, MS_NULL);;
}

/*
 * Device operating function set
 */
static const ms_io_driver_ops_t gpio_drv_ops = {
        .type   = MS_IO_DRV_TYPE_CHR,
        .open   = __gpio_open,
        .close  = __gpio_close,
        .read   = __gpio_read,
        .write  = __gpio_write,
        .ioctl  = __gpio_ioctl,
        .poll   = __gpio_poll,
};

/*
 * Device driver
 */
static ms_io_driver_t gpio_drv = {
        .nnode = {
            .name = "stm32_gpio",
        },
        .ops = &gpio_drv_ops,
};

/*
 * Register gpio device driver
 */
ms_err_t stm32_gpio_drv_register(void)
{
    return ms_io_driver_register(&gpio_drv);
}

/*
 * Create gpio device file
 */
ms_err_t stm32_gpio_dev_create(const char *path, ms_addr_t base, ms_uint8_t pin)
{
    gpio_dev_t *dev;
    ms_err_t err;

    if (IS_GPIO_ALL_INSTANCE((void *)base) && IS_GPIO_ALL_PIN(pin)) {
        dev = ms_kmalloc(sizeof(gpio_dev_t));
        if (dev != MS_NULL) {
            bzero(&dev->priv, sizeof(dev->priv));

            dev->priv.base = base;
            dev->priv.pin  = pin;

            err = ms_io_device_register(&dev->dev, path, "stm32_gpio", &dev->priv);

        } else {
            ms_thread_set_errno(ENOMEM);
            err = MS_ERR;
        }

    } else {
        ms_thread_set_errno(EINVAL);
        err = MS_ERR;
    }

    return err;
}

# 3. GPIO 应用程序

# 3.1 通过按键来控制 LED 灯

打开 GPIO 设备文件,当按下按键时,对应的 LED 点亮:

#include <ms_rtos.h>
#include <string.h>
#include <driver/ms_drv_gpio.h>
#include "test/include/greatest.h"

#define GPIO_A00_DEV_FILE    "/dev/gpio_a0"
#define LED0_GPIO_DEV_FILE   "/dev/gpio_b1"
#define LED1_GPIO_DEV_FILE   "/dev/gpio_b0"
#define KEY0_GPIO_DEV_FILE   "/dev/gpio_h3"
#define KEY1_GPIO_DEV_FILE   "/dev/gpio_h2"
#define KEY2_GPIO_DEV_FILE   "/dev/gpio_c13"

#define GPIO_TEST_COUNT      (100)

int main (int argc, char **argv)
{
    int             fd_ds0, fd_ds1;
    int             fd_key0, fd_key1, fd_key2;
    ms_gpio_param_t param;
    ms_uint8_t      value;
    ms_uint32_t     test_count = GPIO_TEST_COUNT;

    fd_ds0  = ms_io_open(LED0_GPIO_DEV_FILE, O_WRONLY, 0666);
    fd_ds1  = ms_io_open(LED1_GPIO_DEV_FILE, O_WRONLY, 0666);
    fd_key0 = ms_io_open(KEY0_GPIO_DEV_FILE, O_RDONLY, 0666);
    fd_key1 = ms_io_open(KEY1_GPIO_DEV_FILE, O_RDONLY, 0666);
    fd_key2 = ms_io_open(KEY2_GPIO_DEV_FILE, O_RDONLY, 0666);
    if (fd_ds1 < 0 || fd_ds0 < 0 || fd_key1 < 0 || fd_key0 < 0 || fd_key2 < 0) {
        ms_printf("[error]: fd_ds0=%d, fd_ds1=%d, fd_key0=%d, fd_key1=%d, fd_key2=%d\n", 
                  fd_ds0, fd_ds1, fd_key0, fd_key1, fd_key2);
        return  (-1);
    }

    param.mode  = MS_GPIO_MODE_OUTPUT_PP;
    param.pull  = MS_GPIO_PULL_UP;
    param.speed = MS_GPIO_SPEED_HIGH;
    ms_io_ioctl(fd_ds1, MS_GPIO_CMD_SET_PARAM, &param);
    ms_io_ioctl(fd_ds0, MS_GPIO_CMD_SET_PARAM, &param);

    param.mode = MS_GPIO_MODE_INPUT;
    param.pull = MS_GPIO_PULL_UP;
    param.speed = MS_GPIO_SPEED_HIGH;
    ms_io_ioctl(fd_key1, MS_GPIO_CMD_SET_PARAM, &param);
    ms_io_ioctl(fd_key0, MS_GPIO_CMD_SET_PARAM, &param);

    while (test_count--) {
        /*
         * If press down KEY1, then light on LED1
         */
        ms_io_read(fd_key1, &value, sizeof(value));
        ms_io_write(fd_ds1, &value, sizeof(value));

        /*
         * If press down KEY0, then light on LED0
         */
        ms_io_read(fd_key0, &value, sizeof(value));
        ms_io_write(fd_ds0, &value, sizeof(value));

        ms_thread_sleep_ms(200);
    }

    ms_io_close(fd_ds0);
    ms_io_close(fd_ds1);
    ms_io_close(fd_key0);
    ms_io_close(fd_key1);
    ms_io_close(fd_key2);

    return  (0);
}

# 3.2 使用 select 监听 GPIO 中断

打开 GPIO 设备文件,监听中断事件:

#include <ms_rtos.h>
#include <string.h>
#include <driver/ms_drv_gpio.h>
#include "test/include/greatest.h"

#define GPIO_A00_DEV_FILE    "/dev/gpio_a0"
#define LED0_GPIO_DEV_FILE   "/dev/gpio_b1"
#define LED1_GPIO_DEV_FILE   "/dev/gpio_b0"
#define KEY0_GPIO_DEV_FILE   "/dev/gpio_h3"
#define KEY1_GPIO_DEV_FILE   "/dev/gpio_h2"
#define KEY2_GPIO_DEV_FILE   "/dev/gpio_c13"

#define GPIO_TEST_COUNT      (100)

int main (int argc, char **argv)
{
    int             ret;
    int             fd_key;
    ms_gpio_param_t param;
    ms_fd_set_t     efds;
    ms_timeval_t    tv;
    ms_uint32_t     test_count = GPIO_TEST_COUNT;

    fd_key = ms_io_open(GPIO_A00_DEV_FILE, O_RDONLY, 0666);
    if (fd_key < 0) {
        ms_printf("[error]: open device file %s failed!\n", GPIO_A00_DEV_FILE);
        return  (-1);
    }

    param.mode  = MS_GPIO_MODE_IRQ_BOTH;
    param.pull  = MS_GPIO_PULL_NONE;
    param.speed = MS_GPIO_SPEED_HIGH;
    ms_io_ioctl(fd_key, MS_GPIO_CMD_SET_PARAM, &param);

    while (test_count--) {
        FD_ZERO(&efds);
        FD_SET(fd_key, &efds);

        tv.tv_sec  = 2;
        tv.tv_usec = 0;

        ret = ms_io_select(fd_key + 1, NULL, NULL, &efds, &tv);
        if (ret > 0 && FD_ISSET(fd_key, &efds)) {
            ms_printf("[info]: recv external interrupt\n");
        } else {
            ms_printf("[info]: select gpio timeout\n");
        }
    }

    ms_io_close(fd_key);

    return  (0);
}

# 附录(Appendix)

1. Reference

STM32 GPIO 详解:https://blog.csdn.net/dnfestivi/article/details/104984813

GPIO 工作原理:https://blog.csdn.net/qq_38410730/article/details/79858906

GPIO 输入输出:https://blog.csdn.net/baidu_37366055/article/details/80060962

2. FAQ

(1)如何查看当前系统有哪些 GPIO 设备?

GPIO 设备驱动的注册和设备对象的创建是在 BSP 中完成的;MS-RTOS 中的所有设备文件都会存放在 /dev 目录下,用户可以在 shell 命令行中输入 ls /dev 命令来查看当前系统所支持的所有设备。