当前位置: 首页 > news >正文

13、nRF52xx蓝牙学习(GPIOTE组件方式的任务配置)

下面再来探讨下驱动库如何实现任务的配置,驱动库的实现步骤应该和寄存器方式对应,关
键点就是如何调用驱动库的函数。
本例里同样的对比寄存器方式编写两路的 GPOITE 任务输出,一路配置为输出翻转,一路设
置为输出低电平。和 GPIOTE 事件相反,初始化任务应该是输出,同时需要使能任务和触发任务的 驱动库函数。下面介绍下如下三个组件库函数:
(1)nrfx_gpiote_out_init 函数
nrfx_err_t nrfx_gpiote_out_init(nrfx_gpiote_pin_t                pin,nrfx_gpiote_out_config_t const * p_config)
{NRFX_ASSERT(nrf_gpio_pin_present_check(pin));NRFX_ASSERT(m_cb.state == NRFX_DRV_STATE_INITIALIZED);NRFX_ASSERT(p_config);nrfx_err_t err_code = NRFX_SUCCESS;if (pin_in_use(pin)){err_code = NRFX_ERROR_INVALID_STATE;}else{if (p_config->task_pin){int8_t channel = channel_port_alloc(pin, NULL, true);if (channel != NO_CHANNELS){nrf_gpiote_task_configure((uint32_t)channel,pin,p_config->action,p_config->init_state);}else{err_code = NRFX_ERROR_NO_MEM;}}else{pin_in_use_set(pin);}if (err_code == NRFX_SUCCESS){if (p_config->init_state == NRF_GPIOTE_INITIAL_VALUE_HIGH){nrf_gpio_pin_set(pin);}else{nrf_gpio_pin_clear(pin);}nrf_gpio_cfg_output(pin);pin_configured_set(pin);}}NRFX_LOG_INFO("Function: %s, error code: %s.", __func__, NRFX_LOG_ERROR_STRING_GET(err_code));return err_code;
}

nrfx_gpiote_out_init 函数的主要作用是初始化一个 GPIO 引脚作为 GPIOTE(通用外设中断和事件)的输出引脚。它接收一个引脚编号和一个配置结构体指针作为参数,根据配置对引脚进行相应的初始化设置,并返回初始化结果的错误码。

函数参数 • nrfx_gpiote_pin_t pin:要初始化的 GPIO 引脚编号。

• nrfx_gpiote_out_config_t const * p_config:指向 GPIO 输出配置结构体的指针,该结构体包含了引脚的各种配置信息,如是否作为任务引脚、引脚动作、初始状态等。

nrfx_gpiote_out_config_结构体定义如下 :

typedef struct
{nrf_gpiote_polarity_t action;     /**< Configuration of the pin task. */nrf_gpiote_outinit_t  init_state; /**< Initial state of the output pin. */bool                  task_pin;   /**< True if the pin is controlled by a GPIOTE task. */
} nrfx_gpiote_out_config_t;

其中nrf_gpiote_outinit_t是枚举类型,其定义如下 :

typedef enum
{NRF_GPIOTE_INITIAL_VALUE_LOW  = GPIOTE_CONFIG_OUTINIT_Low,       ///<  Low to high.NRF_GPIOTE_INITIAL_VALUE_HIGH = GPIOTE_CONFIG_OUTINIT_High       ///<  High to low.
} nrf_gpiote_outinit_t;

NRFX_ASSERT(nrf_gpio_pin_present_check(pin));
    NRFX_ASSERT(m_cb.state == NRFX_DRV_STATE_INITIALIZED);
    NRFX_ASSERT(p_config);


NRFX_ASSERT 是一个断言宏,用于在开发和调试阶段检查某些条件是否满足。如果条件不满足,程序会触发断言失败,帮助开发者快速定位问题。

nrf_gpio_pin_present_check(pin) 检查指定的引脚是否存在

 m_cb.state == NRFX_DRV_STATE_INITIALIZED 检查 GPIOTE 驱动的状态是否已经初始化。

 p_config 检查配置结构体指针是否有效。


if (pin_in_use(pin))
    {
        err_code = NRFX_ERROR_INVALID_STATE;
    }


pin_in_use(pin) 函数检查指定的引脚是否已经被使用。如果该引脚已经被使用,则将错误码设置为 NRFX_ERROR_INVALID_STATE,表示状态无效。


else
    {
        if (p_config->task_pin)
        {
            int8_t channel = channel_port_alloc(pin, NULL, true);

            if (channel != NO_CHANNELS)
            {
                nrf_gpiote_task_configure((uint32_t)channel,
                                          pin,
                                          p_config->action,
                                          p_config->init_state);
            }
            else
            {
                err_code = NRFX_ERROR_NO_MEM;
            }
        }


如果引脚未被使用,检查配置结构体中的 task_pin 字段。

如果 task_pin 为真,表示该引脚要作为任务引脚使用。

channel_port_alloc(pin, NULL, true) 函数尝试为该引脚分配一个 GPIOTE 通道。如果分配成功,返回通道编号;如果没有可用通道,返回 NO_CHANNELS。

如果通道分配成功,调用 nrf_gpiote_task_configure 函数对该通道进行配置,传入通道编号、引脚编号、引脚动作和初始状态等参数。

如果通道分配失败,将错误码设置为 NRFX_ERROR_NO_MEM,表示内存不足(这里实际是没有可用的 GPIOTE 通道)。


channel_port_alloc函数代码如下:
static int8_t channel_port_alloc(uint32_t pin, nrfx_gpiote_evt_handler_t handler, bool channel)
{int8_t   channel_id = NO_CHANNELS;uint32_t i;uint32_t start_idx = channel ? 0 : GPIOTE_CH_NUM;uint32_t end_idx   =channel ? GPIOTE_CH_NUM : (GPIOTE_CH_NUM + NRFX_GPIOTE_CONFIG_NUM_OF_LOW_POWER_EVENTS);// critical sectionfor (i = start_idx; i < end_idx; i++){if (m_cb.handlers[i] == FORBIDDEN_HANDLER_ADDRESS){pin_in_use_by_te_set(pin, i, handler, channel);channel_id = i;break;}}// critical sectionreturn channel_id;
}

代码解释如下 :

函数概述 channel_port_alloc 是一个静态函数,其作用是为指定的引脚分配一个通道。

该函数会在特定的通道范围内查找可用通道,若找到,就将该通道分配给指定引脚,并返回通道编号;若未找到,就返回 NO_CHANNELS。

函数参数

• pin:类型为 uint32_t,代表要分配通道的引脚编号。

• handler:类型为 nrfx_gpiote_evt_handler_t,是一个事件处理函数指针,当该引脚发生事件时会调用此函数。

• channel:类型为 bool,用于确定通道分配的范围。若为 true,就在普通通道范围内分配;若为 false,就在低功耗事件通道范围内分配。  

若成功分配通道,返回通道编号;若未找到可用通道,返回 NO_CHANNELS。

代码详细解释


static int8_t channel_port_alloc(uint32_t pin, nrfx_gpiote_evt_handler_t handler, bool channel)
{
    int8_t   channel_id = NO_CHANNELS;
    uint32_t i;

    


根据 channel 参数确定通道分配的起始和结束索引

uint32_t start_idx = channel ? 0 : GPIOTE_CH_NUM; 这行代码的作用是根据 channel 的值来确定通道分配范围的起始索引。

• 若 channel 为 true(非零),则 start_idx 被赋值为 0。这意味着通道分配从编号为 0 的通道开始。

• 若 channel 为 false(零),则 start_idx 被赋值为 GPIOTE_CH_NUM。这表明通道分配从编号为 GPIOTE_CH_NUM 的通道开始。

uint32_t end_idx = channel ? GPIOTE_CH_NUM : (GPIOTE_CH_NUM + NRFX_GPIOTE_CONFIG_NUM_OF_LOW_POWER_EVENTS);

这行代码的作用是根据 channel 的值来确定通道分配范围的结束索引。

• 若 channel 为 true(非零),则 end_idx 被赋值为 GPIOTE_CH_NUM。这意味着通道分配的范围是从 0 到 GPIOTE_CH_NUM - 1。

• 若 channel 为 false(零),则 end_idx 被赋值为 GPIOTE_CH_NUM + NRFX_GPIOTE_CONFIG_NUM_OF_LOW_POWER_EVENTS。这表明通道分配的范围是从 GPIOTE_CH_NUM 到 GPIOTE_CH_NUM + NRFX_GPIOTE_CONFIG_NUM_OF_LOW_POWER_EVENTS - 1。

总结 这两行代码的目的是根据 channel 的值选择不同的通道分配范围:

• 当 channel 为 true 时,通道分配范围是从 0 到 GPIOTE_CH_NUM - 1,通常代表普通通道的范围。

• 当 channel 为 false 时,通道分配范围是从 GPIOTE_CH_NUM 到 GPIOTE_CH_NUM + NRFX_GPIOTE_CONFIG_NUM_OF_LOW_POWER_EVENTS - 1,一般代表低功耗事件通道的范围。



    

    // 进入临界区,这里代码注释表示是临界区,但代码中未实际体现临界区的保护代码
    // 临界区用于确保在多线程或多任务环境下,对共享资源的操作是原子的

    // 遍历指定范围的通道
    for (i = start_idx; i < end_idx; i++)
    {
        // 检查当前通道的处理函数地址是否为禁止使用的地址
        if (m_cb.handlers[i] == FORBIDDEN_HANDLER_ADDRESS)
        {
            // 如果是,调用 pin_in_use_by_te_set 函数将该引脚标记为已使用,并关联处理函数
            pin_in_use_by_te_set(pin, i, handler, channel);
            // 将通道编号赋值给 channel_id
            channel_id = i;
            // 找到可用通道后,跳出循环
            break;
        }
    }
    // 退出临界区,同样这里代码注释表示是临界区,但未实际体现临界区的保护代码

    // 返回通道编号,如果未找到可用通道,返回 NO_CHANNELS
    return channel_id;
}
   代码逻辑总结

1. 把 channel_id 初始化为 NO_CHANNELS,这意味着默认情况下未找到可用通道。

2. 依据 channel 参数确定通道分配的起始和结束索引。

3. 进入临界区(代码注释表明是临界区,但未实际实现临界区保护),以此保证在多线程或多任务环境下对共享资源的操作是原子的。

4. 遍历指定范围的通道,检查每个通道的处理函数地址是否为禁止使用的地址。

5. 若找到可用通道,调用 pin_in_use_by_te_set 函数将该引脚标记为已使用,并关联处理函数,然后将通道编号赋值给 channel_id,接着跳出循环。

6. 退出临界区(代码注释表明是临界区,但未实际实现临界区保护)。

7. 返回通道编号,若未找到可用通道,返回 NO_CHANNELS。        


nrf_gpiote_task_configure函数代码如下

__STATIC_INLINE void nrf_gpiote_task_configure(uint32_t idx, uint32_t pin,nrf_gpiote_polarity_t polarity,nrf_gpiote_outinit_t  init_val)
{NRF_GPIOTE->CONFIG[idx] &= ~(GPIOTE_CONFIG_PORT_PIN_Msk |GPIOTE_CONFIG_POLARITY_Msk |GPIOTE_CONFIG_OUTINIT_Msk);NRF_GPIOTE->CONFIG[idx] |= ((pin << GPIOTE_CONFIG_PSEL_Pos) & GPIOTE_CONFIG_PORT_PIN_Msk) |((polarity << GPIOTE_CONFIG_POLARITY_Pos) & GPIOTE_CONFIG_POLARITY_Msk) |((init_val << GPIOTE_CONFIG_OUTINIT_Pos) & GPIOTE_CONFIG_OUTINIT_Msk);
}

 nrf_gpiote_task_configure 是一个静态内联函数,其用途是对 nRF GPIOTE(通用引脚输入 / 输出任务和事件)模块的特定通道配置寄存器进行设置。借助该函数,你能够指定特定通道所关联的引脚、引脚极性以及初始输出值。

函数参数

• idx:uint32_t 类型,代表要配置的 GPIOTE 通道的索引。

• pin:uint32_t 类型,代表要关联到该通道的引脚编号。

• polarity:nrf_gpiote_polarity_t 类型,用于指定引脚的极性,例如高电平触发、低电平触发等。 • init_val:nrf_gpiote_outinit_t 类型,用于指定引脚的初始输出值。  


   
   代码逻辑步骤

1.  清除相关位:

 NRF_GPIOTE->CONFIG[idx] &= ~(GPIOTE_CONFIG_PORT_PIN_Msk |
                             GPIOTE_CONFIG_POLARITY_Msk |
                             GPIOTE_CONFIG_OUTINIT_Msk);
   
  NRF_GPIOTE->CONFIG[idx] 表示 GPIOTE 模块中第 idx 个通道的配置寄存器。  GPIOTE_CONFIG_PORT_PIN_Msk、GPIOTE_CONFIG_POLARITY_Msk 和 GPIOTE_CONFIG_OUTINIT_Msk 分别是用于选择引脚、极性和初始输出值的位掩码。

 ~ 是按位取反运算符,&= 是按位与赋值运算符。这行代码的作用是把配置寄存器中与引脚、极性和初始输出值相关的位清零,为后续设置新值做准备。  

2.  设置新值:

NRF_GPIOTE->CONFIG[idx] |= ((pin << GPIOTE_CONFIG_PSEL_Pos) & GPIOTE_CONFIG_PORT_PIN_Msk) |
                           ((polarity << GPIOTE_CONFIG_POLARITY_Pos) & GPIOTE_CONFIG_POLARITY_Msk) |
                           ((init_val << GPIOTE_CONFIG_OUTINIT_Pos) & GPIOTE_CONFIG_OUTINIT_Msk);
   
  << 是左移运算符,用于把 pin、polarity 和 init_val 移动到配置寄存器中对应的位置。

& 是按位与运算符,用于确保只有对应位掩码覆盖的位被设置。

| 是按位或运算符,|= 是按位或赋值运算符。这行代码的作用是将移动并掩码处理后的 pin、polarity 和 init_val 值设置到配置寄存器中。    

总结 此函数的主要功能是先清除 GPIOTE 通道配置寄存器中与引脚、极性和初始输出值相关的位,然后将新的引脚编号、极性和初始输出值设置到这些位上,从而完成对指定通道的配置。   

     


(2)nrfx_gpiote_out_task_enable函数

void nrfx_gpiote_out_task_enable(nrfx_gpiote_pin_t pin)
{NRFX_ASSERT(nrf_gpio_pin_present_check(pin));NRFX_ASSERT(pin_in_use(pin));NRFX_ASSERT(pin_in_use_by_te(pin));nrf_gpiote_task_enable((uint32_t)m_cb.pin_assignments[pin]);
}


http://www.mrgr.cn/news/98015.html

相关文章:

  • qwen-vl 实现OCR的测试
  • 在 Dev-C++中编译运行GUI 程序介绍(三)有趣示例一组
  • 【力扣hot100题】(089)最长有效括号
  • 在Java项目中,引入【全局异常处理器】
  • QEMU学习之路(6)— RISC-V 启动Linux
  • minio改成https+域名访问
  • 【C++初学】C++核心编程技术详解(二):类与继承
  • Android 自己的智能指针
  • 数据仓库标准库模型架构相关概念浅讲
  • C语言--求n以内的素数(质数)
  • 5️⃣ Coze+AI应用基础教学(2025年全新版本)
  • 自动化测试常用函数
  • Java习题:合并两个有序数组
  • MySQL 进阶 - 2 ( 12000 字详解)
  • C语言超详细指针知识(一)
  • 【学习笔记】头文件中定义函数出现重复定义报错
  • MySQL学习笔记7【InnoDB】
  • 【数据结构】排序
  • <C#> 详细介绍.NET 依赖注入
  • AD9253 LVDS 高速ADC驱动开发