RT-Thread移植到nRF52840的GPIO设备驱动 GPIOTE

《RT-Thread BLE5.0和ANT+应用开发实战指南》的文档已经写了前面19章,目前已经177页,还有15章左右没有写。欢迎各位转载,转载说明出处https://blog.csdn.net/qwea123456/article/details/83247005

RT-Thread移植到nRF52840的GPIO设备驱动 GPIOTE_第1张图片

  1. GPIO 设备驱动(LED 和 KEY)

论坛:bbs.codertown.cn   公众号:Bluetooth-BLE   QQ群:177341833

    GPIO(General Purpose Input Output)通用输入输出,常用的作为输出控制LED,输入作为KEY。对于nordic的GPIO有两种,一种就是普通GPIO还有一种为GPIOTE(GPIO tasks and events),下面先简单介绍一些nRF52840的GPIOTE。

  1. GPIOTE简介

    先看一下GPIO的内部结构图19-1所示。

RT-Thread移植到nRF52840的GPIO设备驱动 GPIOTE_第2张图片

图19-1 GPIO内部结构示意图

    从图19-1 GPIO内部结构,其实很容易了解GPIO的使用,但是对于GPIO的中断和事件有点容易混淆。所以主要看看中断和事件,图中A部分的内部结构由B和C两部分组成,也就是由IN、OUT和CNF寄存器控制的,重点看A部分,这个部分影响到GPIO的中断和事件。GPIO的事件是由引脚的DETECT上产生一个上升沿触发,而DETECT的上升沿由设置在每个GPIO的PIN_CNF寄存器中的SENSE位决定的,但是SENSE只能是检测高电平或者低电平两种极性,所以如果SENSE设置检测高电平,而引脚产生了一个上升沿,硬件会使DETECT产生一个上升沿,从而产生EVENT,EVENT又能产生中断,而有分为EVENT中断和PORT中断。

  1. EVENT中断模式

    nRF2840共有8组事件和任务,也就是说同时只能有8个不同的GPIO产生事件或者任务,当然事件也能触发中断,但是这8组中断基本上是不用的,因为会带来很大的功耗,当调用SDK中的NRFX_GPIOTE_CONFIG_IN_SENSE_LOTOHI(hi_accu)接口时,如果hi_accu=true,就使用的这8组中的一组,也就是所谓的高精度,这样就会带来功耗,如果hi_accu=false就会只使用中断,也就是后面要讲的PORT中断,这时功耗就会降下来。那么GPIO的event和中断有什么关系呢?如果某个引脚使用了GPIOTE,那么它的中断就只能是由event产生。这样来看是不是意味着nRF52840只能同时产生8个GPIO中断呢?肯定不是的,就是下面讲的PORT中断。

  1. PORT中断模式

    PORT中断模式意思就是所有的GPIO都能产生中断,但是中断服务函数只有一个,进入中断后再根据一些条件判断出是哪个产生了中断,同一个引脚只能是EVENT中断或者PORT中断产生,不能同时支持。它是一个低精度中断模式,PORT中断虽然能带来功耗上的优化,但是有可能会漏掉中断,所以特别注意的地方是,因为SENSE只能感知高电平还是低电平,所以在每次设置希望的SENSE之前,先将GPIO设置为相反的状态,另一个注意的当在中断服务函数中进行SENSE变换时,需要程序轮询一次引脚状态,以免漏掉中断,因为这时即使SENSE信号发生改变也不会再触发中断,这个一点在后文驱动中的中断服务函数会有体现。

  1. 基于 pin 设备框架GPIO管理

    在RT-Thread的GPIO设备管理框架是源码/components/drivers/misc 中的pin.cpin.h文件,这个misc文件夹是“Miscellaneous”的前4个字母,即GPIO为混杂设备。要使用pin的杂项文件需要在rtconfig.h中打开RT_USING_PIN。那么底层驱动需要在注册pin设备时传入哪些接口函数呢?这里看一下pin.h文件就知道了。

    在分析pin.h之前先将misc文件夹从RT-Thread源码中拷贝到ZJ-SDK中的RT_THREAD/components/drivers/下面。将008.finsh_shell复制粘贴为009.gpio_driver,在ZJ-SDK/RT_THREAD_NRF_HAL/src文件夹下新建文件空文件rt_nrf_hal_gpio.c,在include文件新建rt_nrf_hal_gpio.h文件。将文件以及头文件添加到009.gpio_driver工程,记得打开宏,就可以进行成功编译了。

  1. pin.h文件

    头文件一般就是一些宏、结构体及一些函数的声明。pin.h源码如代码清单19-1所示。

代码清单19-1 pin.h源码

#ifndef PIN_H__
#define PIN_H__

#include 
#include 

#ifdef __cplusplus
extern "C" {
#endif

/* pin device and operations for RT-Thread */
struct rt_device_pin
{
    struct rt_device parent;
    const struct rt_pin_ops *ops;
};

#define PIN_LOW                 0x00                                            (1)
#define PIN_HIGH                0x01

#define PIN_MODE_OUTPUT         0x00                                           (2)
#define PIN_MODE_INPUT          0x01
#define PIN_MODE_INPUT_PULLUP   0x02
#define PIN_MODE_INPUT_PULLDOWN 0x03
#define PIN_MODE_OUTPUT_OD      0x04

#define PIN_IRQ_MODE_RISING             0x00                                    (3)
#define PIN_IRQ_MODE_FALLING            0x01
#define PIN_IRQ_MODE_RISING_FALLING     0x02
#define PIN_IRQ_MODE_HIGH_LEVEL         0x03
#define PIN_IRQ_MODE_LOW_LEVEL          0x04

#define PIN_IRQ_DISABLE                 0x00                                     (4)
#define PIN_IRQ_ENABLE                  0x01

#define PIN_IRQ_PIN_NONE                -1

struct rt_device_pin_mode
{
    rt_uint16_t pin;
    rt_uint16_t mode;
};
struct rt_device_pin_status
{
    rt_uint16_t pin;
    rt_uint16_t status;
};
struct rt_pin_irq_hdr                                                                 (5)
{
    rt_int16_t        pin;
    rt_uint16_t       mode;
    void (*hdr)(void *args);
    void             *args;
};
struct rt_pin_ops                                                                  (6)
{
    void (*pin_mode)(struct rt_device *device, rt_base_t pin, rt_base_t mode);              (6)-1
    void (*pin_write)(struct rt_device *device, rt_base_t pin, rt_base_t value);               (6)-2
    int (*pin_read)(struct rt_device *device, rt_base_t pin);                               (6)-3

    /* TODO: add GPIO interrupt */
    rt_err_t (*pin_attach_irq)(struct rt_device *device, rt_int32_t pin,            
                      rt_uint32_t mode, void (*hdr)(void *args), void *args);              (6)-4
    rt_err_t (*pin_detach_irq)(struct rt_device *device, rt_int32_t pin);                     (6)-5
    rt_err_t (*pin_irq_enable)(struct rt_device *device, rt_base_t pin, rt_uint32_t enabled);    (6)-6
};

int rt_device_pin_register(const char *name, const struct rt_pin_ops *ops, void *user_data);     (7)

void rt_pin_mode(rt_base_t pin, rt_base_t mode);                                       (8)
void rt_pin_write(rt_base_t pin, rt_base_t value);
int  rt_pin_read(rt_base_t pin);
rt_err_t rt_pin_attach_irq(rt_int32_t pin, rt_uint32_t mode,
                             void (*hdr)(void *args), void  *args);
rt_err_t rt_pin_detach_irq(rt_int32_t pin);
rt_err_t rt_pin_irq_enable(rt_base_t pin, rt_uint32_t enabled);

#ifdef __cplusplus
}
#endif

#endif

代码清单19-1(1):引脚状态宏。

    代码清单19-1(2):引脚方向和驱动能力设置宏。

    代码清单19-1(3):中断极性宏。

    代码清单19-1(4):中断使能控制宏。

    代码清单19-1(5):中断引脚以及对应的中断回调函数结构体。

    代码清单19-1(6):底层需实现的接口。

    代码清单19-1(6)-1:底层实现的设置引脚输入输出,上下来等模式接口。

    代码清单19-1(6)-2:写某个引脚。

    代码清单19-1(6)-3:读某个引脚。

    代码清单19-1(6)-4:中断回调附着接口,这个接口是某个引脚的引脚号、中断模式、中断回调函数以及回调函数的参数存放位置,这样做的目的是,将上层应用和中断服务函数彻底分开,应用层不用管中断服务函数中的事情,中断服务函数只需根据中断引脚调用这个引脚的回调函数即可。

    代码清单19-1(6)-5:关闭中断时,将附着的中断引脚信息清空。

    代码清单19-1(7)-6:使能中断控制接口。

    代码清单19-1(7):pin设备注册函数,底层通过调用这个函数将设备名称、结构体const struct rt_pin_ops以及用户数据指针传给内核进行设备管理。

    代码清单19-1(8):应用层调用接口。

    从上面来看,底层应用要实现的就是const struct rt_pin_ops结构体中的6个函数接口。

  1. gpio底层驱动分析

    这个rt_nrf_hal_gpio.c.h文件需要根据nRF52840的数据手册以及NORDIC官方SDK进行调试和编写,下面直接分析我调试出来的驱动源码。

rt_nrf_hal_gpio.h源码分析,它的源码如代码清单19-2所示。

代码清单19-2 rt_nrf_hal_gpio.h源码

#ifndef _RT_nRF_HAL_GPIO_H_
#define _RT_nRF_HAL_GPIO_H_

#define RT_GPIO_SENSE_Pos (0UL) /*!< Position of SENSE field. */
#define RT_GPIO_SENSE_Msk (0x3UL << RT_GPIO_DRI_SENSE_Pos) /*!< Bit mask of SENSE field. */

#define RT_GPIO_POLARITY_Pos (2UL) /*!< Position of SENSE field. */
#define RT_GPIO_POLARITY_Msk (0x3UL << RT_GPIO_DRI_POLARITY_Pos) /*!< Bit mask of SENSE field. */

#define RT_GPIO_MODE_SET(polarity,sense)   (((polarity << RT_GPIO_POLARITY_Pos) & RT_GPIO_POLARITY_Msk) | ((sense << RT_GPIO_SENSE_Pos) & RT_GPIO_SENSE_Msk)) /*!< Bit mask of SENSE field. */                                                                            (1)
 
#define ZJ_LED1    NRF_GPIO_PIN_MAP(0,13)                                           (2)
#define ZJ_LED2    NRF_GPIO_PIN_MAP(0,14)
#define ZJ_LED3    NRF_GPIO_PIN_MAP(0,15)
#define ZJ_LED4    NRF_GPIO_PIN_MAP(0,16)

#define ZJ_KEY1    NRF_GPIO_PIN_MAP(0,11)                                           (3)
#define ZJ_KEY2    NRF_GPIO_PIN_MAP(0,12)
#define ZJ_KEY3    NRF_GPIO_PIN_MAP(0,13)
#define ZJ_KEY4    NRF_GPIO_PIN_MAP(0,14)

#endif

 代码清单19-2(1):这个宏非常重要,前面已经调到,NORDIC的gpio中断不是直接由引脚的上升下降沿产生,换句话说就是在代码清单19-1(5)结构体中的mode需要用传入NORDIC GPIO设置的极性和SENSCE两个状态才能确定是哪个GPIO产生的中断。所以在调用代码清单19-1(6)-4接口时需要调用这个宏,以及在中断服务函数中,确定是哪个引脚产生的中断时需要使用上面的宏。

    代码清单19-2(2):开发板的4个灯。

    代码清单19-2(3):开发板的4个按键。

rt_nrf_hal_gpio.c源码分析,目前这个只支持PORT中断,对于高精度的EVENT_IN没有在中断函数中处理。它的源码如代码清单19-3所示。

代码清单19-3 rt_nrf_hal_gpio.c源码

#include "board.h"
#include "rt_nrf_hal_gpio.h"
#include "nrf_drv_common.h"
#include "app_util_platform.h"
#include "nrfx_gpiote.h"
#include "nrf_gpio.h"
#include 
#include 
#include 
#include "nrf52840.h"
#include "core_cm4.h"
#include "nrf_log.h"
#include "nrf_log_ctrl.h"
#include "nrf_log_default_backends.h"
#include "nrf_bitmask.h"

static void _nrf_pin_mode(struct rt_device *device, rt_base_t pin, rt_base_t mode)                   (1)
{
    switch(mode)
    {
      case PIN_MODE_OUTPUT:
      {
         /* output setting */
         nrf_gpio_cfg_output(pin);
      }
      break;

      case PIN_MODE_INPUT:         
      {
         /* input setting: not pull. */
         nrf_gpio_cfg_input(pin,NRF_GPIO_PIN_NOPULL);
      }
      break;

      case PIN_MODE_INPUT_PULLUP:
      {
         /* input setting: pull up. */
         nrf_gpio_cfg_input(pin,NRF_GPIO_PIN_PULLUP);
      }
      break;

      case PIN_MODE_INPUT_PULLDOWN:
      {
         /* input setting: pull up. */
         nrf_gpio_cfg_input(pin,NRF_GPIO_PIN_PULLDOWN);
      }
      break;

      case PIN_MODE_OUTPUT_OD:    
      {
        /* output setting: od. */
      }
      break;

      default:
      break;
   }   
}

static void _nrf_pin_write(struct rt_device *device, rt_base_t pin, rt_base_t value)                   (2)
{
    nrf_gpio_pin_write(pin,value);
}

static int _nrf_pin_read(struct rt_device *device, rt_base_t pin)                                   (3)
{
   return nrf_gpio_pin_read(pin);
}

struct rt_pin_irq_hdr pin_irq_hdr_tab[P0_PIN_NUM+P1_PIN_NUM] =                             (4)
{
    /*P0*/
    { -1, 0, RT_NULL, RT_NULL},
    { -1, 0, RT_NULL, RT_NULL},
    { -1, 0, RT_NULL, RT_NULL},
    { -1, 0, RT_NULL, RT_NULL},
    { -1, 0, RT_NULL, RT_NULL},
    { -1, 0, RT_NULL, RT_NULL},
    { -1, 0, RT_NULL, RT_NULL},
    { -1, 0, RT_NULL, RT_NULL},
    { -1, 0, RT_NULL, RT_NULL},
    { -1, 0, RT_NULL, RT_NULL},
    { -1, 0, RT_NULL, RT_NULL},
    { -1, 0, RT_NULL, RT_NULL},
    { -1, 0, RT_NULL, RT_NULL},
    { -1, 0, RT_NULL, RT_NULL},
    { -1, 0, RT_NULL, RT_NULL},
    { -1, 0, RT_NULL, RT_NULL},

    { -1, 0, RT_NULL, RT_NULL},
    { -1, 0, RT_NULL, RT_NULL},
    { -1, 0, RT_NULL, RT_NULL},
    { -1, 0, RT_NULL, RT_NULL},
    { -1, 0, RT_NULL, RT_NULL},
    { -1, 0, RT_NULL, RT_NULL},
    { -1, 0, RT_NULL, RT_NULL},
    { -1, 0, RT_NULL, RT_NULL},
    { -1, 0, RT_NULL, RT_NULL},
    { -1, 0, RT_NULL, RT_NULL},
    { -1, 0, RT_NULL, RT_NULL},
    { -1, 0, RT_NULL, RT_NULL},
    { -1, 0, RT_NULL, RT_NULL},
    { -1, 0, RT_NULL, RT_NULL},
    { -1, 0, RT_NULL, RT_NULL},
    { -1, 0, RT_NULL, RT_NULL},

    /*P1*/
    { -1, 0, RT_NULL, RT_NULL},
    { -1, 0, RT_NULL, RT_NULL},
    { -1, 0, RT_NULL, RT_NULL},
    { -1, 0, RT_NULL, RT_NULL},
    { -1, 0, RT_NULL, RT_NULL},
    { -1, 0, RT_NULL, RT_NULL},
    { -1, 0, RT_NULL, RT_NULL},
    { -1, 0, RT_NULL, RT_NULL},
    { -1, 0, RT_NULL, RT_NULL},
    { -1, 0, RT_NULL, RT_NULL},
    { -1, 0, RT_NULL, RT_NULL},
    { -1, 0, RT_NULL, RT_NULL},
    { -1, 0, RT_NULL, RT_NULL},
    { -1, 0, RT_NULL, RT_NULL},
    { -1, 0, RT_NULL, RT_NULL},
    { -1, 0, RT_NULL, RT_NULL},
};

static rt_err_t _nrf_pin_attach_irq(struct rt_device *device, rt_int32_t pin,
                           rt_uint32_t mode, void (*hdr)(void *args), void *args)               (5)
{
    rt_base_t level;
    rt_int32_t pinindex = pin;

    level = rt_hw_interrupt_disable();
    if (pin_irq_hdr_tab[pinindex].pin == pin &&
            pin_irq_hdr_tab[pinindex].hdr == hdr &&
            pin_irq_hdr_tab[pinindex].mode == mode &&
            pin_irq_hdr_tab[pinindex].args == args)
    {
        rt_hw_interrupt_enable(level);
        return RT_EOK;
    }
    if (pin_irq_hdr_tab[pinindex].pin != -1)
    {
        rt_hw_interrupt_enable(level);
        return RT_EBUSY;
    }
    pin_irq_hdr_tab[pinindex].pin = pin;
    pin_irq_hdr_tab[pinindex].hdr = hdr;
    pin_irq_hdr_tab[pinindex].mode = mode;
    pin_irq_hdr_tab[pinindex].args = args;
    rt_hw_interrupt_enable(level);

    return RT_EOK;
}

static rt_err_t _nrf_pin_detach_irq(struct rt_device *device, rt_int32_t pin)                        (6)
{
    rt_base_t level;
    rt_int32_t pinindex = pin;

    level = rt_hw_interrupt_disable();
    if (pin_irq_hdr_tab[pinindex].pin == -1)
    {
        rt_hw_interrupt_enable(level);
        return RT_EOK;
    }
    pin_irq_hdr_tab[pinindex].pin = -1;
    pin_irq_hdr_tab[pinindex].hdr = RT_NULL;
    pin_irq_hdr_tab[pinindex].mode = 0;
    pin_irq_hdr_tab[pinindex].args = RT_NULL;
    rt_hw_interrupt_enable(level);

    return RT_EOK;
}

static rt_err_t _nrf_pin_irq_enable(struct rt_device *device, rt_base_t pin, rt_uint32_t enabled)       (7)
{
    rt_base_t level;
    rt_int32_t pinindex = pin;
    nrf_gpio_pin_sense_t sense;
    nrf_gpio_pin_pull_t config; 
    if (enabled == PIN_IRQ_ENABLE)
    {     
        level = rt_hw_interrupt_disable();
        if (pin_irq_hdr_tab[pinindex].pin == -1)
        {
            rt_hw_interrupt_enable(level);
            return RT_ENOSYS;
        }

        switch ((pin_irq_hdr_tab[pinindex].mode >>  RT_GPIO_POLARITY_Pos) & 0x03)
        {
           case NRF_GPIOTE_POLARITY_HITOLO:
               sense = NRF_GPIO_PIN_SENSE_LOW;
               config = NRF_GPIO_PIN_PULLUP;
               break;
           case NRF_GPIOTE_POLARITY_LOTOHI:
               sense = NRF_GPIO_PIN_SENSE_HIGH;
               config = NRF_GPIO_PIN_PULLDOWN;
               break;
           case NRF_GPIOTE_POLARITY_TOGGLE:                                           (7)-1
               /* read current pin state and set for next sense to oposit */
               sense = (nrf_gpio_pin_read(pin)) ? NRF_GPIO_PIN_SENSE_LOW : NRF_GPIO_PIN_SENSE_HIGH;
               config = (sense == NRF_GPIO_PIN_SENSE_LOW)?NRF_GPIO_PIN_PULLUP:NRF_GPIO_PIN_PULLDOWN;
               break;
           default:
               break;
        }
        nrf_gpio_cfg_sense_input(pin,config,sense);
        NVIC_SetPriority(GPIOTE_IRQn, 5);
        NVIC_EnableIRQ(GPIOTE_IRQn);
        nrf_gpiote_event_clear(NRF_GPIOTE_EVENTS_PORT);
        nrf_gpiote_int_enable(GPIOTE_INTENSET_PORT_Msk);
        rt_hw_interrupt_enable(level);
    }
    else if (enabled == PIN_IRQ_DISABLE)
    {
        nrf_gpiote_event_clear(NRF_GPIOTE_EVENTS_PORT);
        nrf_gpiote_int_disable(NRF_GPIOTE_INT_PORT_MASK);
        NVIC_DisableIRQ(GPIOTE_IRQn);
    }
    else
    {
        return RT_ENOSYS;
    }

    return RT_EOK;
}

const static struct rt_pin_ops _nrf_pin_ops =
{
   _nrf_pin_mode,
   _nrf_pin_write,
   _nrf_pin_read,
   _nrf_pin_attach_irq,
   _nrf_pin_detach_irq,
   _nrf_pin_irq_enable
};

int rt_hw_pin_init(void)                                                                (8)
{
   int ret = RT_EOK;
   ret = rt_device_pin_register("nrfpin", &_nrf_pin_ops, RT_NULL);
   return ret;
}
INIT_BOARD_EXPORT(rt_hw_pin_init);

typedef void (*rt_evt_handler_t)(void *args);
void nrfx_gpiote_irq_handler(void)                                                        (9)
{
    uint8_t i;
    uint32_t status            = 0;
    uint32_t input[GPIO_COUNT] = {0};  

    /* collect PORT status event, if event is set read pins state. Processing is postponed to the
     * end of interrupt. */
    if (nrf_gpiote_event_is_set(NRF_GPIOTE_EVENTS_PORT))                                  (9)-1
    {
        nrf_gpiote_event_clear(NRF_GPIOTE_EVENTS_PORT);                                 (9)-2
        status |= (uint32_t)NRF_GPIOTE_INT_PORT_MASK;
        nrf_gpio_ports_read(0, GPIO_COUNT, input);                                        (9)-3
    }

    if (status & (uint32_t)NRF_GPIOTE_INT_PORT_MASK)                                      (9)-4
    {
        /* Process port event. */
        uint32_t port_idx;   
        uint8_t  repeat                  = 0;
        uint32_t toggle_mask[GPIO_COUNT] = {0};                                           (9)-5
        uint32_t pins_to_check[GPIO_COUNT];

        // Faster way of doing memset because in interrupt context.
        for (port_idx = 0; port_idx < GPIO_COUNT; port_idx++)                                 (9)-6
        {
            pins_to_check[port_idx] = 0xFFFFFFFF;
        }

        do
        {
            repeat = 0;

            for (i = 0; i < P0_PIN_NUM+P1_PIN_NUM; i++)  
            {
                uint8_t           polarity_and_sense = (uint8_t)pin_irq_hdr_tab[i].mode;       (9)-7
                nrfx_gpiote_pin_t pin           = pin_irq_hdr_tab[i].pin;                     (9)-8
                if (pin != -1 && nrf_bitmask_bit_is_set(pin, pins_to_check))                     (9)-9
                {
                    nrf_gpiote_polarity_t polarity = (nrf_gpiote_polarity_t)((polarity_and_sense >> RT_GPIO_POLARITY_Pos) & 0x03);                                                          (9)-10
                    rt_evt_handler_t handler =  pin_irq_hdr_tab[i].hdr;                       (9)-11
                    if (handler || (polarity == NRF_GPIOTE_POLARITY_TOGGLE))                 (9)-12
                    {
                        if (polarity == NRF_GPIOTE_POLARITY_TOGGLE)                        (9)-13
                        {
                            nrf_bitmask_bit_set(pin, toggle_mask);                           (9)-14
                        }
                        nrf_gpio_pin_sense_t sense     = nrf_gpio_pin_sense_get(pin);         (9)-15
                        uint32_t             pin_state = nrf_bitmask_bit_is_set(pin, input);     (9)-16
                        if ((pin_state && (sense == NRF_GPIO_PIN_SENSE_HIGH)) ||             (9)-17
                          (!pin_state && (sense == NRF_GPIO_PIN_SENSE_LOW)))              (9)-18
                        {
                           //NRF_LOG_INFO("PORT event for pin: %d, polarity: %d.", pin, polarity);
                            if (polarity == NRF_GPIOTE_POLARITY_TOGGLE)                  (9)-19
                            {
                                nrf_gpio_pin_sense_t next_sense =
                                    (sense == NRF_GPIO_PIN_SENSE_HIGH) ?
                                    NRF_GPIO_PIN_SENSE_LOW :
                                    NRF_GPIO_PIN_SENSE_HIGH;
                                nrf_gpio_cfg_sense_set(pin, next_sense);                    (9)-20
                                ++repeat;
                            }
                            if (handler)
                            {
                                uint32_t args = polarity<<6|pin;                           (9)-21
                                handler((void*)args);                                    (9)-22
                            }
                        }
                    }
                }
            }

            if (repeat)                                                                 (9)-23
            {
                // When one of the pins in low-accuracy and toggle mode becomes active,
                // it's sense mode is inverted to clear the internal SENSE signal.
                // State of any other enabled low-accuracy input in toggle mode must be checked
                // explicitly, because it does not trigger the interrput when SENSE signal is active.
                // For more information about SENSE functionality, refer to Product Specification.    (9)-24

                uint32_t new_input[GPIO_COUNT];
                bool     input_unchanged = true;
                nrf_gpio_ports_read(0, GPIO_COUNT, new_input);

                // Faster way of doing memcmp because in interrupt context.
                for (port_idx = 0; port_idx < GPIO_COUNT; port_idx++)
                {
                    if (new_input[port_idx] != input[port_idx])                              (9)-25
                    {
                        input_unchanged = false;
                        break;
                    }
                }

                if (input_unchanged)
                {
                    // No change.
                    repeat = 0;                                                       (9)-26
                }
                else
                {
                    // Faster way of doing memcpy because in interrupt context.
                    for (port_idx = 0; port_idx < GPIO_COUNT; port_idx++)
                    {
                        input[port_idx]         = new_input[port_idx];
                        pins_to_check[port_idx] = toggle_mask[port_idx];                   (9)-27
                    }
                }
            }
        }
        while (repeat);
    }
}

代码清单19-3(1):GPIO模式设置。

    代码清单19-3(2):GPIO写。

    代码清单19-3(3):GPIO读。

    代码清单19-3(4):GPIO的中断引脚,模式以及回调函数存储数组,下面就叫这个GPIO回调数组。这里共有32+16个元素,因为共有P0端口引脚32个,P1端口引脚16个,这里其实可以通过宏进行大小控制,使用了几个中断口就开启设置几个元素,以节省内存。

    代码清单19-3(5):附着引脚到GPIO回调数组,

    代码清单19-3(6):从GPIO回调数组中脱离引脚。

    代码清单19-3(7):中断使能接口。

    代码清单19-3(7)-1:双边沿触发中断时,由于GPIO的SENSE只支持高电平和低电平,也就是上升沿和下降沿触发,所以在双边沿时,需要代码乒乓处理。

    代码清单19-3(8):pin设备注册。

    代码清单19-3(9):中断服务函数。

    代码清单19-3(9)-1:判断PORT中断。

    代码清单19-3(9)-2:清PORT中断。

    代码清单19-3(9)-3:读取32+16个引脚的状态,后文需要判断是哪个引脚发送了中断。

    代码清单19-3(9)-4:处理PORT中断。

    代码清单19-3(9)-5:引脚是不是双边沿触发,因为需要手动将SENSE状态切换。

    代码清单19-3(9)-6:这变量是为了双边沿触发用的,这里先认为所有的引脚都是双边沿触发。

    代码清单19-3(9)-7:从GPIO回调数组中读到引脚的极性和SENSE。

    代码清单19-3(9)-8:从GPIO回调数组得到引脚。

    代码清单19-3(9)-9:引脚是不是已经被用了并判断引脚是不是需要检查。

    代码清单19-3(9)-10:得到引脚极性。

    代码清单19-3(9)-11:得到引脚的回调函数。

    代码清单19-3(9)-12:判断回调函数是否有效,或者是否是双边沿触发。

    代码清单19-3(9)-13\14:如果是双边沿触发,就为下一次进来做准备。

    代码清单19-3(9)-15:得到SENSE。

    代码清单19-3(9)-16:读取引脚状态。

    代码清单19-3(9)-17\18:根据SENSE和引脚状态判断是否这个引脚产生的中断。

    代码清单19-3(9)-19\20:判断是不是双边沿触发,如果是就要进行手动切换SENCE以及极性。

    代码清单19-3(9)-21:这里注意,因为回调函数中需要引脚的极性和引脚号,但是参数只有是一个指针,所以这里把极性左移6位(极性2bits,1:LoToHi,2:HiToLove,3:Toggle),然后与上引脚号,这里引脚号中的6bits的最低5bits是引脚号,而第5bit为0时表示P0端口,为1表示P1端口。这里直接将这个求与之后的值强转为一个指针,只有只需在应用层回调函数中,把这个指针强转为32位的数据即可。

    代码清单19-3(9)-22:调用应用层的回调。

    代码清单19-3(9)-23:是不是双边沿触发,如果是就需要再次读取GPIO状态,并再中断中循环一次。

    代码清单19-3(9)-24:这个注释说明了必须轮询一次,因为SENSE不会再产生中断。

    代码清单19-3(9)-25:判断引脚有没有变化,这里判断的是所有引脚是不是有变化,如果有变化,就在执行以便中断服务函数。

    代码清单19-3(9)-26:如果没有变化,说明这次中断处理完成,跳出中断服务函数。

    代码清单19-3(9)-26:这里得到哪个引脚发送了变化,循环中断服务函数时就只需判断这一个引脚了。

  1. GPIO设备驱动验证

    上面两个文件写完之后,只需要在写一些应用层代码就能进行验证了,这里修改app_init.c文件,同时将之前在app_init.h文件中定义的LED灯的宏删掉,因为已经在rt_nrf_hal_gpio.h中定义了。app_init.c修改如代码清单19-4。

代码清单19-4 app_init.c源码

#include 
#include 
#include "nordic_common.h"
#include "nrf.h"
#include "app_util_platform.h"
#include "nrf_gpio.h"
#include "nrf_log.h"
#include "nrf_log_ctrl.h"
#include "nrf_log_default_backends.h"
#include 
#include "board.h"
#include "app_init.h"
#include "app_uart.h"
#include "rt_nrf_hal_gpio.h"
#include 
#include "nrfx_gpiote.h"

void key_handle(void *args)                                                            (1)
{
   static bool pin_toggle=false;
   nrf_gpiote_polarity_t polarity = (uint32_t)args >>6;                                      (1)-1
   nrfx_gpiote_pin_t pin =  (uint32_t)args & 0x3F;                                        (1)-2
   if(pin == ZJ_KEY1 && polarity == NRF_GPIOTE_POLARITY_HITOLO)                          (1)-3
   {
      rt_pin_write(ZJ_LED2, pin_toggle);                                                (1)-4
      pin_toggle = !pin_toggle;
//      rt_kprintf("ZJ_KEY1 INT");
   }
}
//FINSH_FUNCTION_EXPORT(key_handle, key_handle(139) led2 will toggle)
FINSH_FUNCTION_EXPORT_ALIAS(key_handle, key,  key_handle(139) led2 will toggle)            (1)-5


int led_cortrol(uint8_t ctl )                                                             (2)
{
    if(ctl == 31)
    {
       rt_pin_write(ZJ_LED3, 0);
    }
    else if(ctl == 30)
    {
       rt_pin_write(ZJ_LED3, 1);
    }
    else if(ctl == 41)
    {
       rt_pin_write(ZJ_LED4, 0);
    }
    else if(ctl == 40)
    {
       rt_pin_write(ZJ_LED4, 1);
    }
}
//FINSH_FUNCTION_EXPORT(led_cortrol, led_cortrol led3(31/30) and led4(41/40))
FINSH_FUNCTION_EXPORT_ALIAS(led_cortrol, led, led_cortrol led3(31/30) and led4(41/40))          (2)-1

void nrf_log_thread_init();
int main(void)
{
    // Initialize.
    uint32_t cnt=0;
    bool pin_toggle=false;
    nrf_log_thread_init();
    NRF_LOG_INFO("RT-Thread for nrf52840 started.");
    rt_pin_mode(ZJ_LED1,PIN_MODE_OUTPUT);                                           (3)
    rt_pin_mode(ZJ_LED2,PIN_MODE_OUTPUT);
    rt_pin_mode(ZJ_LED3,PIN_MODE_OUTPUT);
    rt_pin_mode(ZJ_LED4,PIN_MODE_OUTPUT);

    rt_pin_write(ZJ_LED1,PIN_HIGH);                                                    (4)
    rt_pin_write(ZJ_LED2,PIN_HIGH);
    rt_pin_write(ZJ_LED3,PIN_HIGH);
    rt_pin_write(ZJ_LED4,PIN_HIGH);

    rt_pin_attach_irq(ZJ_KEY1, RT_GPIO_MODE_SET(NRF_GPIOTE_POLARITY_HITOLO,NRF_GPIO_PIN_SENSE_LOW),key_handle, RT_NULL);  (5)
    rt_pin_irq_enable(ZJ_KEY1, PIN_IRQ_ENABLE);                                          (6)
     
    NRF_LOG_INFO("RT-Thread for nrf52840 started");
    while(1)
    {
       rt_pin_write(ZJ_LED1, pin_toggle); 
       rt_thread_mdelay(500);
//       NRF_LOG_INFO("This is nrf_log test %d.",cnt);
//       rt_kprintf("This is rt_kprintf test %d.",cnt);
//       cnt++;
        pin_toggle = !pin_toggle;
    }
    return RT_TRUE;
}

代码清单19-4(1):按键中断回调函数。

    代码清单19-4(1)-1:得到中断pin的极性。

    代码清单19-4(1)-2:得到中断pin。

    代码清单19-4(1)-3:判断是不是KEY1中断。

    代码清单19-4(1)-4:翻转LED2。

    代码清单19-4(1)-5:将key_handle导入finsh,并重命名为key。可以使用finsh进行调试,调用key_handle(139) 就相当于KEY1中断,139=1<<6|11 =10001011,这个就能看到LED2进行翻转。通过按键KEY1也能控制LED2翻转。

    代码清单19-4(2):使用finsh控制LED3和LED4亮灭。

    代码清单19-4(2)-1:将led_cortrol导入finsh。

    代码清单19-4(3):配置LED灯。

    代码清单19-4(4):关闭LED灯。

    代码清单19-4(4):附着KEY1到GPIO中断数组。

    代码清单19-4(5):使能KEY1中断。

    将工程编译下载到开发板,能看到LED1在闪烁。按键能控制LED2亮灭,但是因为没有消抖,所以不稳定,下面使用finsh进行调试。

    在下载后打开终端,然后输入tab键就能看到key和led导入到 了finsh。输入list_device()回车就能看到已挂载的设备信息。如图19-2所示。key和led调试信息如图19-3所示。

RT-Thread移植到nRF52840的GPIO设备驱动 GPIOTE_第3张图片

图19-2 nrfpin设备挂载

RT-Thread移植到nRF52840的GPIO设备驱动 GPIOTE_第4张图片

图19-3 finsh调试GPIO设备驱动

 

RT-Thread移植到nRF52840的GPIO设备驱动 GPIOTE_第5张图片     RT-Thread移植到nRF52840的GPIO设备驱动 GPIOTE_第6张图片    RT-Thread移植到nRF52840的GPIO设备驱动 GPIOTE_第7张图片

论坛                                       公众号                                     QQ群

 

你可能感兴趣的:(NORDIC,RT-Thread学习)