10、nRF52xx蓝牙学习(GPIOTE事件模式中断组件)
#include <stdbool.h>
#include "nrf.h"
#include "nrf_drv_gpiote.h"
#include "app_error.h"
#include "boards.h"
/*
#ifdef BSP_BUTTON_0:
这是一个条件编译指令,其作用是检查 BSP_BUTTON_0 这个宏是否已经被定义。BSP 一般代表板级支持包(Board Support Package),BSP_BUTTON_0 可能是板子上某个按钮所对应的引脚编号。
#define PIN_IN BSP_BUTTON_0:若 BSP_BUTTON_0 已被定义,此指令会将 PIN_IN 定义为 BSP_BUTTON_0 的值。如此一来,后续代码里就能用 PIN_IN 来代表输入引脚。
#ifndef PIN_IN:该指令用于检查 PIN_IN 这个宏是否未被定义。
#error "Please indicate input pin":要是 PIN_IN 未被定义,编译器会停止编译,并输出错误信息 “Please indicate input pin”,以此提示开发者要明确指定输入引脚。*/
#ifdef BSP_BUTTON_0//检查 BSP_BUTTON_0 这个宏是否已经被定义,(在pca10056中有定义)#define PIN_IN BSP_BUTTON_0//若 BSP_BUTTON_0 已被定义,此指令会将 PIN_IN 定义为 BSP_BUTTON_0 的值
#endif
#ifndef PIN_IN//要是 PIN_IN 未被定义#error "Please indicate input pin"
#endif#ifdef BSP_LED_0#define PIN_OUT BSP_LED_0
#endif
#ifndef PIN_OUT#error "Please indicate output pin"
#endif/**GPIOTE中断处理in_pin_handler,这是一个回调函数,通常用于处理 GPIO(通用输入输出)引脚事件。当 GPIO 引脚发生特定事件时,该函数会被调用。 nrf_drv_gpiote_pin_t pin:表示触发事件的 GPIO 引脚编号。nrf_drv_gpiote_pin_t 是一个自定义的数据类型,用于表示 GPIO 引脚。 nrf_gpiote_polarity_t action:表示触发事件的极性,比如上升沿触发、下降沿触发等。nrf_gpiote_polarity_t 也是一个自定义的数据类型,用于表示极性。 函数体if(nrf_gpio_pin_read(PIN_IN) == 0) // 按键防抖
{nrf_gpio_pin_toggle(PIN_OUT);
}
按键防抖检查: nrf_gpio_pin_read(PIN_IN):调用 nrf_gpio_pin_read 函数读取 PIN_IN 引脚的电平状态。PIN_IN 是之前代码中通过预处理指令定义的输入引脚。
if(nrf_gpio_pin_read(PIN_IN) == 0):检查 PIN_IN 引脚的电平是否为低电平(通常按键按下时引脚电平为低)。这里的判断起到了简单的按键防抖作用,
因为按键按下时可能会产生抖动,通过检查引脚电平是否持续为低,可以减少误触发的可能性。
?引脚状态切换: nrf_gpio_pin_toggle(PIN_OUT):如果 PIN_IN 引脚的电平为低,调用 nrf_gpio_pin_toggle 函数切换 PIN_OUT 引脚的电平状态。
PIN_OUT 是之前代码中通过预处理指令定义的输出引脚。也就是说,当输入引脚检测到按键按下时,会切换输出引脚的电平,通常用于控制 LED 灯的亮灭。
总结 这段代码定义了一个 GPIO 引脚事件处理函数,当输入引脚检测到按键按下(低电平)时,会切换输出引脚的电平状态,实现了简单的按键控制功能,同时包含了基本的按键防抖逻辑。*/
void in_pin_handler(nrf_drv_gpiote_pin_t pin, nrf_gpiote_polarity_t action)//nrfx_gpiote_pin_t 其实就是uint32_t的意思,是为了便于理解,才这样取了一个别名
{if(nrf_gpio_pin_read(PIN_IN)== 0)//按键防抖{nrf_gpio_pin_toggle(PIN_OUT);//led反转}
}
/**
配置GPIOTE初始化*/
static void gpio_init(void)
{nrf_gpio_cfg_output(PIN_OUT);//设置led引脚为输出ret_code_t err_code;//typedef ret_code_t nrfx_err_t;//typedef 原类型 新类型名;err_code = nrf_drv_gpiote_init();APP_ERROR_CHECK(err_code);nrf_drv_gpiote_in_config_t in_config = GPIOTE_CONFIG_IN_SENSE_TOGGLE(true);in_config.pull = NRF_GPIO_PIN_PULLUP;err_code = nrf_drv_gpiote_in_init(PIN_IN, &in_config, in_pin_handler);APP_ERROR_CHECK(err_code);nrf_drv_gpiote_in_event_enable(PIN_IN, true);
}/**
主函数,循环等待中断*/
int main(void)
{gpio_init();while (true){// Do nothing.}
} 程序理解:
1、
#ifdef BSP_BUTTON_0
#define PIN_IN BSP_BUTTON_0
#endif
#ifndef PIN_IN
#error "Please indicate input pin"
#endif
#ifdef BSP_LED_0
#define PIN_OUT BSP_LED_0
#endif
#ifndef PIN_OUT
#error "Please indicate output pin"
#endif
#ifdef BSP_BUTTON_0:
这是一个条件编译指令,其作用是检查 BSP_BUTTON_0 这个宏是否已经被定义(经查询发现在pca10056.h中有定义)。BSP 一般代表板级支持包(Board Support Package),BSP_BUTTON_0 可能是板子上某个按钮所对应的引脚编号。
#define PIN_IN BSP_BUTTON_0:
若 BSP_BUTTON_0 已被定义,此指令会将 PIN_IN 定义为 BSP_BUTTON_0 的值。如此一来,后续代码里就能用 PIN_IN 来代表输入引脚。
#ifndef PIN_IN:
该指令用于检查 PIN_IN 这个宏是否未被定义。
#error "Please indicate input pin":
要是 PIN_IN 未被定义,编译器会停止编译,并输出错误信息 “Please indicate input pin”,以此提示开发者要明确指定输入引脚。
2、配置GPIOTE初始化
static void gpio_init(void)
{
nrf_gpio_cfg_output(PIN_OUT);//设置led引脚为输出
ret_code_t err_code;//typedef ret_code_t nrfx_err_t;//typedef 原类型 新类型名;
err_code = nrf_drv_gpiote_init();
APP_ERROR_CHECK(err_code);
nrf_drv_gpiote_in_config_t in_config = GPIOTE_CONFIG_IN_SENSE_TOGGLE(true);
in_config.pull = NRF_GPIO_PIN_PULLUP;
err_code = nrf_drv_gpiote_in_init(PIN_IN, &in_config, in_pin_handler);
APP_ERROR_CHECK(err_code);
nrf_drv_gpiote_in_event_enable(PIN_IN, true);
}
(1)nrf_gpio_cfg_output函数是nrf_gpio.h中定义的一个函数,其作用是将指定的引脚配置为输出模式。
(2)ret_code_t err_code是定义一个ret_code_t类型的变量err_code,
ret_code_t 是一个自定义的数据类型,通常用于表示函数执行的返回状态码。ret_code_t有一个别名是nrfx_err_t,查询有下面的信息:
/typedef ret_code_t nrfx_err_t
nrfx_err_t是一个枚举类型,其内容如下:
typedef enum {NRFX_SUCCESS = (NRFX_ERROR_BASE_NUM + 0), ///< Operation performed successfully.NRFX_ERROR_INTERNAL = (NRFX_ERROR_BASE_NUM + 1), ///< Internal error.NRFX_ERROR_NO_MEM = (NRFX_ERROR_BASE_NUM + 2), ///< No memory for operation.NRFX_ERROR_NOT_SUPPORTED = (NRFX_ERROR_BASE_NUM + 3), ///< Not supported.NRFX_ERROR_INVALID_PARAM = (NRFX_ERROR_BASE_NUM + 4), ///< Invalid parameter.NRFX_ERROR_INVALID_STATE = (NRFX_ERROR_BASE_NUM + 5), ///< Invalid state, operation disallowed in this state.NRFX_ERROR_INVALID_LENGTH = (NRFX_ERROR_BASE_NUM + 6), ///< Invalid length.NRFX_ERROR_TIMEOUT = (NRFX_ERROR_BASE_NUM + 7), ///< Operation timed out.NRFX_ERROR_FORBIDDEN = (NRFX_ERROR_BASE_NUM + 8), ///< Operation is forbidden.NRFX_ERROR_NULL = (NRFX_ERROR_BASE_NUM + 9), ///< Null pointer.NRFX_ERROR_INVALID_ADDR = (NRFX_ERROR_BASE_NUM + 10), ///< Bad memory address.NRFX_ERROR_BUSY = (NRFX_ERROR_BASE_NUM + 11), ///< Busy.NRFX_ERROR_ALREADY_INITIALIZED = (NRFX_ERROR_BASE_NUM + 12), ///< Module already initialized.NRFX_ERROR_DRV_TWI_ERR_OVERRUN = (NRFX_ERROR_DRIVERS_BASE_NUM + 0), ///< TWI error: Overrun.NRFX_ERROR_DRV_TWI_ERR_ANACK = (NRFX_ERROR_DRIVERS_BASE_NUM + 1), ///< TWI error: Address not acknowledged.NRFX_ERROR_DRV_TWI_ERR_DNACK = (NRFX_ERROR_DRIVERS_BASE_NUM + 2) ///< TWI error: Data not acknowledged.
} nrfx_err_t; (3) nrfx_gpiote_init 函数的作用是初始化通用外设 I/O 任务和事件 (GPIOTE) 驱动程序。
该函数会对 GPIOTE 相关的状态、引脚使用情况、通道等进行初始化设置,同时配置中断优先级并使能中断。
(4)nrfx_gpiote_in_init 函数用于初始化一个 GPIO(通用输入输出)引脚,使其作为 GPIOTE(通用外设中断和事件)输入引脚。
它会检查引脚是否可用,分配一个 GPIOTE 通道,根据配置对引脚进行相应设置,并记录操作结果。
函数参数
pin:类型为 nrfx_gpiote_pin_t,表示要初始化的 GPIO 引脚编号。
p_config:指向 nrfx_gpiote_in_config_t 类型的常量结构体指针,包含了该引脚的配置信息,如是否高精度模式、是否跳过 GPIO 设置、上拉 / 下拉配置等。
evt_handler:类型为 nrfx_gpiote_evt_handler_t,是一个事件处理函数指针,当该引脚触发事件时会调用此函数。
(5)APP_ERROR_CHECK(err_code);
经查询有如下宏定义:
#define APP_ERROR_HANDLER(ERR_CODE) \do \{ \app_error_handler_bare((ERR_CODE)); \} while (0)
#endif
/**@brief Macro for calling error handler function if supplied error code any other than NRF_SUCCESS.** @param[in] ERR_CODE Error code supplied to the error handler.*/
#define APP_ERROR_CHECK(ERR_CODE) \do \{ \const uint32_t LOCAL_ERR_CODE = (ERR_CODE); \if (LOCAL_ERR_CODE != NRF_SUCCESS) \{ \APP_ERROR_HANDLER(LOCAL_ERR_CODE); \} \} while (0)/**@brief Macro for calling error handler function if supplied boolean value is false.** @param[in] BOOLEAN_VALUE Boolean value to be evaluated.*/
#define APP_ERROR_CHECK_BOOL(BOOLEAN_VALUE) \do \{ \const uint32_t LOCAL_BOOLEAN_VALUE = (BOOLEAN_VALUE); \if (!LOCAL_BOOLEAN_VALUE) \{ \APP_ERROR_HANDLER(0); \} \} while (0) #define APP_ERROR_CHECK(ERR_CODE) \
do \
{ \
const uint32_t LOCAL_ERR_CODE = (ERR_CODE); \
if (LOCAL_ERR_CODE != NRF_SUCCESS) \
{ \
APP_ERROR_HANDLER(LOCAL_ERR_CODE); \
} \
} while (0)
APP_ERROR_CHECK宏,它在嵌入式系统或者其他需要严格错误处理的程序中非常常见,这个宏用于检查传入的错误码 ERR_CODE 是否为 NRF_SUCCESS(通常表示操作成功)。若错误码不等于 NRF_SUCCESS,就调用 APP_ERROR_HANDLER 宏来处理该错误。
#define APP_ERROR_HANDLER(ERR_CODE) \
do \
{ \
app_error_handler_bare((ERR_CODE)); \
} while (0)
APP_ERROR_HANDLER(ERR_CODE)宏,此宏的作用是调用 app_error_handler_bare 函数来处理错误。app_error_handler_bare 函数应当是一个自定义的错误处理函数,负责对传入的错误码进行处理,比如输出错误信息、记录日志或者进行复位操作等。
经查询app_error_handler_bare 函数是app_error.c中定义的一个函数,其代码如下:
void app_error_handler_bare(ret_code_t error_code)
{error_info_t error_info ={.line_num = 0,.p_file_name = NULL,.err_code = error_code,};app_error_fault_handler(NRF_FAULT_ID_SDK_ERROR, 0, (uint32_t)(&error_info));UNUSED_VARIABLE(error_info);
} 此函数中用到的error_info_t是一个结构体类型,其在app_error.c中的定义如下:
typedef struct
{uint32_t line_num; /**< The line number where the error occurred. */uint8_t const * p_file_name; /**< The file in which the error occurred. */uint32_t err_code; /**< The error code representing the error that occurred. */
} error_info_t; app_error_fault_handler函数app_error_weak.c中定义如下:
__WEAK void app_error_fault_handler(uint32_t id, uint32_t pc, uint32_t info)
{__disable_irq();NRF_LOG_FINAL_FLUSH();#ifndef DEBUGNRF_LOG_ERROR("Fatal error");
#elseswitch (id){
#if defined(SOFTDEVICE_PRESENT) && SOFTDEVICE_PRESENTcase NRF_FAULT_ID_SD_ASSERT:NRF_LOG_ERROR("SOFTDEVICE: ASSERTION FAILED");break;case NRF_FAULT_ID_APP_MEMACC:NRF_LOG_ERROR("SOFTDEVICE: INVALID MEMORY ACCESS");break;
#endifcase NRF_FAULT_ID_SDK_ASSERT:{assert_info_t * p_info = (assert_info_t *)info;NRF_LOG_ERROR("ASSERTION FAILED at %s:%u",p_info->p_file_name,p_info->line_num);break;}case NRF_FAULT_ID_SDK_ERROR:{error_info_t * p_info = (error_info_t *)info;NRF_LOG_ERROR("ERROR %u [%s] at %s:%u\r\nPC at: 0x%08x",p_info->err_code,nrf_strerror_get(p_info->err_code),p_info->p_file_name,p_info->line_num,pc);NRF_LOG_ERROR("End of error report");break;}default:NRF_LOG_ERROR("UNKNOWN FAULT at 0x%08X", pc);break;}
#endifNRF_BREAKPOINT_COND;// On assert, the system can only recover with a reset.#ifndef DEBUGNRF_LOG_WARNING("System reset");NVIC_SystemReset();
#elseapp_error_save_and_stop(id, pc, info);
#endif // DEBUG
} 此函数的简单解释:
这个函数是一个弱定义的错误处理函数,用于处理系统中出现的严重错误。当系统遇到致命错误时,会调用此函数进行相应的错误处理,如记录错误信息、根据调试模式采取不同的恢复措施等。 函数定义和参数
__WEAK void app_error_fault_handler(uint32_t id, uint32_t pc, uint32_t info)
• __WEAK:这是一个弱定义修饰符,意味着该函数可以被用户在其他地方重新定义。若用户重新定义了此函数,链接时会使用用户定义的版本;若没有重新定义,则使用此处的默认版本。
• app_error_fault_handler:函数名,用于处理系统的严重错误。
• uint32_t id:错误类型的标识符,用于区分不同类型的错误。
• uint32_t pc:程序计数器(Program Counter)的值,它记录了错误发生时程序执行的位置。
• uint32_t info:额外的错误信息,根据不同的错误类型,这个信息可能包含不同的内容。
函数主体
1. 禁用中断和刷新日志
__disable_irq();
NRF_LOG_FINAL_FLUSH();
• __disable_irq():禁用所有中断,防止在错误处理过程中被其他中断打断,确保错误处理的完整性。
• NRF_LOG_FINAL_FLUSH():将日志缓冲区中的所有日志信息刷新到输出设备(如串口),确保在系统进入错误处理状态前,所有的日志信息都能被记录下来。
2. 根据调试模式处理错误
#ifndef DEBUG
NRF_LOG_ERROR("Fatal error");
#else
switch (id)
{
#if defined(SOFTDEVICE_PRESENT) && SOFTDEVICE_PRESENT
case NRF_FAULT_ID_SD_ASSERT:
NRF_LOG_ERROR("SOFTDEVICE: ASSERTION FAILED");
break;
case NRF_FAULT_ID_APP_MEMACC:
NRF_LOG_ERROR("SOFTDEVICE: INVALID MEMORY ACCESS");
break;
#endif
case NRF_FAULT_ID_SDK_ASSERT:
{
assert_info_t * p_info = (assert_info_t *)info;
NRF_LOG_ERROR("ASSERTION FAILED at %s:%u",
p_info->p_file_name,
p_info->line_num);
break;
}
case NRF_FAULT_ID_SDK_ERROR:
{
error_info_t * p_info = (error_info_t *)info;
NRF_LOG_ERROR("ERROR %u [%s] at %s:%u\r\nPC at: 0x%08x",
p_info->err_code,
nrf_strerror_get(p_info->err_code),
p_info->p_file_name,
p_info->line_num,
pc);
NRF_LOG_ERROR("End of error report");
break;
}
default:
NRF_LOG_ERROR("UNKNOWN FAULT at 0x%08X", pc);
break;
}
#endif
• 非调试模式(#ifndef DEBUG):直接记录一个致命错误日志。
• 调试模式(#else 分支):使用 switch 语句根据 id 的值来判断错误类型,并记录相应的错误信息。
NRF_FAULT_ID_SD_ASSERT:软设备断言失败,记录相应的错误信息。 ◦ NRF_FAULT_ID_APP_MEMACC:软设备内存访问无效,记录相应的错误信息。 ◦ NRF_FAULT_ID_SDK_ASSERT:SDK 断言失败,将 info 转换为 assert_info_t 类型的指针,从中提取出错的文件名和行号,并记录错误信息。
NRF_FAULT_ID_SDK_ERROR:SDK 错误,将 info 转换为 error_info_t 类型的指针,从中提取错误码、错误描述、出错的文件名和行号,以及程序计数器的值,并记录详细的错误信息。 default:未知错误,记录错误发生的程序计数器的值。
3. 断点条件
NRF_BREAKPOINT_COND;
这是一个断点条件,在调试时可以触发断点,方便开发者进行调试。
4. 根据调试模式进行恢复操作
#ifndef DEBUG
NRF_LOG_WARNING("System reset");
NVIC_SystemReset();
#else
app_error_save_and_stop(id, pc, info);
#endif // DEBUG
• 非调试模式(#ifndef DEBUG):记录系统即将复位的警告信息,然后调用 NVIC_SystemReset() 函数进行系统复位,使系统重新启动。
• 调试模式(#else 分支):调用 app_error_save_and_stop(id, pc, info) 函数,该函数可能会保存错误信息并停止系统运行,方便开发者进行调试和分析。
总结 app_error_fault_handler 函数是一个用于处理系统严重错误的通用函数,它会根据不同的错误类型记录详细的错误信息,并根据调试模式采取不同的恢复措施。在非调试模式下,系统会直接复位;在调试模式下,系统会保存错误信息并停止运行,以便开发者进行调试。
(4) nrfx_gpiote_in_config_t是nrfx_gpiote.c中定义的一个结构类型,定义如下:
typedef struct
{nrf_gpiote_polarity_t sense; /**< Transition that triggers the interrupt. */nrf_gpio_pin_pull_t pull; /**< Pulling mode. */bool is_watcher : 1; /**< True when the input pin is tracking an output pin. */bool hi_accuracy : 1; /**< True when high accuracy (IN_EVENT) is used. */bool skip_gpio_setup : 1; /**< Do not change GPIO configuration */
} nrfx_gpiote_in_config_t; (5)GPIOTE_CONFIG_IN_SENSE_TOGGLE是一个nrfx_gpiote.c中定义宏,其内容如下 :
#define NRFX_GPIOTE_CONFIG_IN_SENSE_TOGGLE(hi_accu) \
{ \.sense = NRF_GPIOTE_POLARITY_TOGGLE, \.pull = NRF_GPIO_PIN_NOPULL, \.is_watcher = false, \.hi_accuracy = hi_accu, \.skip_gpio_setup = false, \
} 宏的解释如下:
这段代码定义了一个宏 NRFX_GPIOTE_CONFIG_IN_SENSE_TOGGLE,其主要用途是生成一个用于配置 GPIO 任务和事件(GPIOTE)输入引脚的结构体初始化列表。
下面对代码进行详细解释。
宏定义概述
#define NRFX_GPIOTE_CONFIG_IN_SENSE_TOGGLE(hi_accu) \
{ \
.sense = NRF_GPIOTE_POLARITY_TOGGLE, \
.pull = NRF_GPIO_PIN_NOPULL, \
.is_watcher = false, \
.hi_accuracy = hi_accu, \
.skip_gpio_setup = false, \
}
此宏接收一个参数 hi_accu,它会返回一个初始化好的结构体,用于配置 GPIOTE 输入引脚的各项参数。
代码逐行解释
1. 宏定义与参数
#define NRFX_GPIOTE_CONFIG_IN_SENSE_TOGGLE(hi_accu)
NRFX_GPIOTE_CONFIG_IN_SENSE_TOGGLE 是宏的名称,调用该宏可生成 GPIOTE 输入引脚的配置结构体。
• hi_accu 是宏的参数,代表高准确性模式的开关状态,它会被用于初始化结构体中的 hi_accuracy 成员。
2. 结构体成员初始化
2.1 检测极性(.sense)
.sense = NRF_GPIOTE_POLARITY_TOGGLE,
• .sense 是结构体中的一个成员,用于指定 GPIOTE 输入引脚的检测极性。
• NRF_GPIOTE_POLARITY_TOGGLE 表示该引脚会检测电平的翻转(即从高电平到低电平或者从低电平到高电平的变化)。
2.2 上拉 / 下拉电阻(.pull)
.pull = NRF_GPIO_PIN_NOPULL,
• .pull 成员用于配置引脚的上拉或下拉电阻。
• NRF_GPIO_PIN_NOPULL 表明不使用上拉或下拉电阻,引脚处于浮空状态。
2.3 观察器模式(.is_watcher)
.is_watcher = false,
• .is_watcher 成员用于指定该引脚是否处于观察器模式。 • false 表示不启用观察器模式。
2.4 高准确性模式(.hi_accuracy)
.hi_accuracy = hi_accu,
• .hi_accuracy 成员用于指定是否启用高准确性模式。
• hi_accu 是宏的参数,调用宏时传入的值会被赋给该成员。
2.5 跳过 GPIO 设置(.skip_gpio_setup)
.skip_gpio_setup = false,
• .skip_gpio_setup 成员用于指定是否跳过 GPIO 的基本设置。
• false 表示不跳过,会进行正常的 GPIO 设置。
总结 NRFX_GPIOTE_CONFIG_IN_SENSE_TOGGLE 宏提供了一种便捷的方式来配置 GPIOTE 输入引脚,使其检测电平的翻转。通过传入不同的 hi_accu 参数,可以灵活控制是否启用高准确性模式。在实际使用时,这个宏可以与相关的 GPIOTE 初始化函数结合,对 GPIO 引脚进行快速配置。
例如:
nrfx_gpiote_in_config_t config = NRFX_GPIOTE_CONFIG_IN_SENSE_TOGGLE(true);
这样就创建了一个配置结构体 config,该结构体将 GPIOTE 输入引脚配置为检测电平翻转,不使用上拉或下拉电阻,不启用观察器模式,启用高准确性模式,并且不跳过 GPIO 的基本设置。
(6)rr_code = nrf_drv_gpiote_in_init(PIN_IN, &in_config, in_pin_handler);
nrf_drv_gpiote_in_init 函数用于根据 in_config 结构体中的配置信息初始化指定的输入引脚 PIN_IN。
• PIN_IN 是之前通过预处理指令定义的一个宏,代表要配置为输入的引脚,通常连接到一个按键。
• &in_config 是输入配置结构体的指针,传递给函数以指定输入引脚的具体配置。
• in_pin_handler 是一个回调函数,当输入引脚检测到指定的电平变化时,该函数会被调用。开发者可以在 in_pin_handler 函数中实现具体的处理逻辑,比如控制 LED 灯的状态。
(7)nrf_drv_gpiote_in_event_enable(PIN_IN, true);
nrf_drv_gpiote_in_event_enable 函数用于启用指定输入引脚的事件检测功能。
• PIN_IN 是要启用事件检测的输入引脚。
• true 表示启用事件检测,这样当 PIN_IN 引脚的电平发生翻转时,就会触发 in_pin_handler 回调函数。
该函数在7、nRF52xx蓝牙学习(nrf_gpiote.c库函数学习)中单独有说明。
(8)in_pin_handler函数代码
void in_pin_handler(nrf_drv_gpiote_pin_t pin, nrf_gpiote_polarity_t action)//nrfx_gpiote_pin_t 其实就是uint32_t的意思,是为了便于理解,才这样取了一个别名
{if(nrf_gpio_pin_read(PIN_IN)== 0)//按键防抖{nrf_gpio_pin_toggle(PIN_OUT);//led反转}
} 这段代码定义了一个名为 in_pin_handler 的函数,它是一个 GPIO(通用输入输出)引脚事件处理函数,通常作为回调函数使用,用于处理特定 GPIO 引脚的电平变化事件。
下面为你详细解释代码各部分:
函数定义和参数
void in_pin_handler(nrf_drv_gpiote_pin_t pin, nrf_gpiote_polarity_t action)
• 函数名:in_pin_handler,它是专门处理 GPIO 引脚事件的函数。
• 参数: ◦ nrf_drv_gpiote_pin_t pin:此参数代表触发事件的 GPIO 引脚编号。nrf_drv_gpiote_pin_t 是自定义的数据类型,用于标识 GPIO 引脚。
◦ nrf_gpiote_polarity_t action:该参数表示触发事件的极性,也就是引脚电平变化的类型,例如上升沿触发(从低电平变为高电平)或者下降沿触发(从高电平变为低电平)。nrf_gpiote_polarity_t 同样是自定义的数据类型。
函数体 按键状态检查
if(nrf_gpio_pin_read(PIN_IN) == 0)
• nrf_gpio_pin_read 是一个函数,其作用是读取指定 GPIO 引脚的当前电平状态。
• PIN_IN 是一个宏定义,代表输入引脚,通常连接着一个按键。
• if(nrf_gpio_pin_read(PIN_IN) == 0) 用于检查 PIN_IN 引脚的电平是否为低电平。在常见的按键电路中,按键按下时引脚电平会被拉低,所以这里通过检查低电平来判断按键是否被按下。同时,这也起到了简单的按键防抖作用,因为按键按下时可能会产生抖动,持续检测低电平可以减少误触发的可能性。
切换输出引脚状态
nrf_gpio_pin_toggle(PIN_OUT);
• nrf_gpio_pin_toggle 是一个函数,用于切换指定 GPIO 引脚的电平状态。
• PIN_OUT 是一个宏定义,代表输出引脚,通常连接着一个 LED 灯。
• 当 PIN_IN 引脚检测到低电平(即按键按下)时,调用 nrf_gpio_pin_toggle(PIN_OUT) 函数会切换 PIN_OUT 引脚的电平状态。如果 PIN_OUT 引脚原来是高电平,调用后会变为低电平;如果原来是低电平,调用后会变为高电平。这样就实现了通过按键控制 LED 灯亮灭状态的切换。
总结 in_pin_handler 函数是一个 GPIO 引脚事件处理函数,当输入引脚 PIN_IN 检测到按键按下(低电平)时,会切换输出引脚 PIN_OUT 的电平状态,从而实现按键控制 LED 灯亮灭的功能,同时包含了简单的按键防抖逻辑。
相关文章:
10、nRF52xx蓝牙学习(GPIOTE事件模式中断组件)
由于驱动组件库是可以直接调用的,那么编程者的任务就只有编写主函数 main。 #include <stdbool.h> #include "nrf.h" #include "nrf_drv_gpiote.h" #include "app_error.h" #include "boards.h" /* #ifdef BSP_BUTTO…...
第7篇:Linux程序访问控制FPGA端LEDR<五>
Q:如何设计.c程序代码实现FPGA端外设LEDR流水灯? A:在DE1-SoC开发板上实现的流水灯效果:一次只点亮一个红色LED,初始状态为向左移动直至点亮LEDR9,然后改变移动的方向为向右直至点亮LEDR0,以此…...
类名与协议名相同,开发中应该避免吗?
在 Objective-C 开发中,协议与实现类之间的命名关系非常重要。虽然语言允许协议名和类名相同,但从可读性和维护性等角度出发,这种做法并不推荐。本文通过一个典型示例展开分析,并提供更合理的命名建议。 一、示例 在某项目中&…...
linux下io操作详细解析
在 Linux 系统下,IO(输入/输出)操作是程序与外部设备(如文件、网络等)交互的重要方式。Linux 提供了丰富的系统调用和库函数来支持各种 IO 操作。以下是对 Linux 下 IO 操作的详细解析,包括文件 IO、网络 I…...
Unity 实现伤害跳字
核心组件: Dotween TextMeshPro 过程轨迹如下图: 代码如下: using System.Collections; using System.Collections.Generic; using DG.Tweening; using TMPro; using UnityEngine; using UnityEngine.Pool;public class …...
Java集合框架:核心接口与关系全解析
精心整理了最新的面试资料和简历模板,有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 一、集合框架概述 Java集合框架(Java Collections Framework, JCF)是Java中用于存储、操作和管理数据集合的核心工具库。它提供了一套…...
008二分答案+贪心判断——算法备赛
二分答案贪心判断 有些问题,从已知信息推出答案,细节太多,过程繁杂,不易解答。 从猜答案出发,贪心地判断该答案是否合法是个不错的思路,这要求所有可能的答案是单调的(例:x满足条件…...
计算机视觉与深度学习 | 视觉SLAM学习思路总结与视觉SLAM发展历程(1986年至2025年)
视觉SLAM(Simultaneous Localization and Mapping,同时定位与建图)是计算机视觉和机器人领域的重要研究方向,涉及数学、几何、优化、传感器融合等多学科知识。以下是学习视觉SLAM的系统化思路总结,适合从入门到进阶的学习路径:视觉SLAM学习思路总结 一、基础准备 数学基…...
衣橱管理助手系统(衣服推荐系统)(springboot+ssm+vue+mysql)含运行文档
衣橱管理助手系统(衣服推荐系统)(springbootssmvuemysql)含运行文档 该系统名为衣橱管理助手,是一个衣物搭配管理系统,主要功能包括衣物档案管理、衣物搭配推荐、搭配收藏以及套装智能推荐。用户可以通过系统进行衣物的搭配和收藏管理,系统提…...
学习笔记四——Rust 函数通俗入门
🦀 Rust 函数通俗入门 📘 Rust 是一门语法精炼但设计严谨的系统级语言。本文围绕函数这一主线,带你真正搞懂 Rust 最关键的语法思想,包括表达式驱动、闭包捕获、Trait 限制、生命周期标注与所有权规则,每遇到一个新概念…...
【场景应用3】audio_classification:音频分类的微调
1 引言 本笔记展示了如何对多语种预训练的语音模型进行微调,以实现自动语音识别(Automatic Speech Recognition)。 本笔记旨在使用SUPERB数据集中的关键词检测子集,并且可以使用任何来自模型库(Model Hub)的语音模型检查点,只要该模型有一个包含序列分类头(Sequence …...
文件上传做题记录
1,[SWPUCTF 2021 新生赛]easyupload2.0 直接上传php 再试一下phtml 用蚁剑连发现连不上 那就只要命令执行了 2,[SWPUCTF 2021 新生赛]easyupload1.0 当然,直接上传一个php是不行的 phtml也不行,看下是不是前端验证,…...
【Pandas】pandas DataFrame to_numpy
Pandas2.2 DataFrame Conversion 方法描述DataFrame.astype(dtype[, copy, errors])用于将 DataFrame 中的数据转换为指定的数据类型DataFrame.convert_dtypes([infer_objects, …])用于将 DataFrame 中的数据类型转换为更合适的类型DataFrame.infer_objects([copy])用于尝试…...
Vue环境搭建:vue+idea
目录 第一章、Vue环境搭建:安装node2.1)node的下载2.2)配置node的环境变量2.3)常见的npm命令 第二章、使用idea创建vue工程2.1)在IDEA中设置国内镜像2.2)在IDEA中进行脚手架安装2.3)在IDEA中创建…...
ECMAScript 7~10 新特性
ECMAScript 7 新特性 ECMAScript 6 新特性(一) ECMAScript 6 新特性(二) ECMAScript 7~10 新特性(本文) 1. 数组方法 Array.prototype.includes() 用来检测数组中是否包含指定元素,返回布尔值&…...
银河麒麟v10(arm架构)部署Embedding模型bge-m3【简单版本】
硬件 服务器配置:鲲鹏2 * 920(32c) 4 * Atlas300I duo卡 参考文章 https://www.hiascend.com/developer/ascendhub/detail/07a016975cc341f3a5ae131f2b52399d 鲲鹏昇腾Atlas300Iduo部署Embedding模型和Rerank模型并连接Dify(自…...
Manifold-IJ 2022.1.21 版本解析:IntelliJ IDEA 的 Java 增强插件指南
Manifold-IJ-2022.1.21 可能是 IntelliJ IDEA 的一个插件或相关版本,特别是与 Manifold 这个增强 Java 开发体验的框架相关的组件。 很多时候没有网络环境,而又需要这个插件。 Manifold-IJ 2022.1.21下载:https://pan.quark.cn/s/ad907344c…...
轻量级碎片化笔记memos本地NAS部署与跨平台跨网络同步笔记实战
文章目录 前言1. 使用Docker部署memos2. 注册账号与简单操作演示3. 安装cpolar内网穿透4. 创建公网地址5. 创建固定公网地址 推荐 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。 点击跳转到网站 前言…...
【C++算法】54.链表_合并 K 个升序链表
文章目录 题目链接:题目描述:解法C 算法代码: 题目链接: 23. 合并 K 个升序链表 题目描述: 解法 解法一:暴力解法 每个链表的平均长度为n,有k个链表,时间复杂度O(nk^2) 合并两个有序…...
EG8200Mini-104边缘计算网关!聚焦IEC104协议的工业数据转换与远程运维平台
在工业自动化和信息化融合不断深化的背景下,现场设备的数据采集与协议转换能力对系统集成效率与运维成本产生着直接影响。EG8200Mini-104边缘计算网关正是基于此需求场景设计,具备IEC104主从站双向支持能力,并配套远程运维与多网络接入方案&a…...
python多线程+异步编程让你的程序运行更快
多线程简介 多线程是Python中实现并发编程的重要方式之一,它允许程序在同一时间内执行多个任务。在某些环境中使用多线程可以加快我们代码的执行速度,例如我们通过爬虫获得了一个图片的url数组,但是如果我们一个一个存储很明显会非常缓慢&…...
各种场景的ARP攻击描述笔记(超详细)
1、ARP报文限速 上一章我们说过ARP报文也是需要上送CPU进行处理的协议报文,如果设备对收到的大量ARP报文全部进行处理,可能导致CPU负荷过重而无法处理其他业务。因此,在处理之前需要对ARP报文进行限速,以保护CPU资源。 1.根据源MAC地址或源IP地址进行ARP限速 当设备检测到某一…...
Java 实现 List<String> 与 String 互转
在 Java 开发过程中,有时需要将 List<String> 转为 String 存储,后续使用时再还原回去。此时就需要 Java 实现 List<String> 与 String 互转。以下是一种互转方式。 采用如下工具包实现。 <dependency><groupId>org.apache.com…...
庙算兵推:使用Streamlit框架构建了一个智能作战推演系统。
这段代码是一个完整的军事模拟应用,使用Streamlit框架构建了一个智能作战推演系统。该系统包括了三维地图显示、作战单位管理、应急事件处理等功能。用户可以通过界面控制推演的开始和暂停,调整时间加速倍率,并查看实时的战斗情况和系统状态。…...
daz3d ERC Freeze to Morph Target 和 另存为 Morph Asset(s)
. ERC 冻结至变形目标 (ERC Freeze to Morph Target) 核心目标:将骨架的调整与自定义造型的滑块关联起来。 详细解释: 当你创建一个自定义造型(Morph)并调整了骨架(Rigging)以适应这个新造型后ÿ…...
HDCP(四)
HDCP驱动开发实战深度解析 以下从协议栈架构、核心模块实现、安全设计到硬件集成,结合HDCP 2.x规范与主流硬件平台(如ARM、FPGA)特性,系统拆解驱动开发关键环节: 1. 协议栈架构与模块划分 驱动分层设计 硬件抽象层&…...
Docker MySQL的主从同步 数据备份 数据同步 配置文件
创建主库 docker run \--namemysql_1 \-e MYSQL_ROOT_PASSWORD123456 \-p 3306:3306 \-v mysql_main_data:/var/lib/mysql \--restart unless-stopped \-d \mysql:8.0进入容器内部 docker exec -it mysql_1 bash查找配置文件 find / -name my.cnf复制出主机 docker cp mysql…...
MATLAB在哪些特定领域比Python更有优势?
文章目录 前言科学研究与工程计算数值计算信号处理控制系统设计 教育领域易于学习和上手教学资源丰富 快速原型开发集成开发环境便捷 前言 MATLAB 在以下特定领域比 Python 更具优势: 科学研究与工程计算 数值计算 高效矩阵运算:MATLAB 以矩阵为基本数…...
linux安装mysql常出现的问题
wget http://repo.mysql.com/mysql-community-release-el7-5.noarch.rpm rpm -ivh mysql-community-release-el7-5.noarch.rpm yum update yum install mysql-server 权限设置: chown -R mysql:mysql /var/lib/mysql/ 初始化 MySQL: mysqld --initiali…...
C++手撕单链表及逆序打印
在学习数据结构的过程中,链表是一个非常重要的基础数据结构。今天,我们将通过C手动实现一个单链表,并添加一个逆序打印的功能,帮助大家更好地理解链表的实现和操作。 一、链表简介 链表是一种线性数据结构,其中每个元…...
