过滤中断的最佳方法
Best way to filter interrupts
这里有点基于意见的问题...
我目前正在使用一个 uController(cc1310),它在单个回调中实现所有 32 个引脚的变化中断。
通过为给定的引脚启用中断,将调用所述回调,我们需要检查哪个引脚产生了中断,方法是:
/* gpio_interrupt_callback is registered in the interrupt vector */
void gpio_interrupt_callback( void )
{
uint32_t pinMask = HWREG( GPIO_BASE + GPIO_O_EVFLAGS31_0 );
if (pinMask & 0x00000001)
{
/* pin_0 generated interrupt */
}
...
}
此时pinMask
中的每一位都是对应的pin中断标志
直到现在,每个应用程序只需要一个 pin/process 来使用 ioc 事件,所以每次我们需要它时,我们只需注册我们的回调就可以了。
最后一个应用程序将使用更多,所以我正在考虑制作一个 GPIO_interrupt 模块,我可以在其中为给定的 pin 注册一个回调,该模块将通过 pinMask
和调用正确的回调。
所以...
大师们建议哪个选项?
a)
我只能为我使用的中断添加 if's
typedef void (*pin_interrupt_callback_t)( void );
pin_interrupt_callback_t pin0_callback;
/* gpio_interrupt_callback is registered in the interrupt vector */
void gpio_interrupt_callback( void )
{
uint32_t pinMask = HWREG( GPIO_BASE + GPIO_O_EVFLAGS31_0 );
if (pinMask & 0x00000001)
{
HWREG( GPIO_BASE + GPIO_O_EVFLAGS31_0 ) |= 0x00000001;
/* pin_0 generated interrupt */
if(NULL != pin0_callback)
pin0_callback();
}
else if (pinMask & 0x00000002)
{
...
}
...
}
b)
这样更动态一些?,我可以在其中添加或删除回调 to/from table
(A table 也可以用在 a 中) 不过)
typedef void (*pin_interrupt_callback_t)( void );
pin_interrupt_callback_t pin_callback[32] = {
pin0_callback,
pin1_callback,
...
}
/* gpio_interrupt_callback is registered in the interrupt vector */
void gpio_interrupt_callback( void )
{
uint32_t pinMask = HWREG( GPIO_BASE + GPIO_O_EVFLAGS31_0 );
for(uint8_t i = 0; i < 32; i++)
{
if(pinMask & (1 << i))
if(NULL != pin_callback[i])
pin_callback[i]();
}
}
c)
这种方式是最复杂的一种,但我只会循环现有的中断,我可以为几个引脚注册相同的回调(也可以在a中这样做)和b),但这里如果同时发生多个中断只会调用一次)
我们甚至可以为每个引脚注册多个回调
typedef void (*pin_interrupt_callback_t)( uint32_t mask );
typedef struct pin_callback_element
{
uint32_t callbackMask;
pin_callback_element *next;
pin_interrupt_callback_t callback;
} pin_callback_element_t;
pin_callback_element_t *pin_callbacks = NULL;
/* elem must be declared as `static pin_callback_table_t elem;` */
uint8_t register_callback(
pin_callback_element_t *elem,
uint32_t pinMask,
pin_interrupt_callback_t callback )
{
/* Make sure no element is registered for a NULL callback */
elem->next = NULL;
elem->callbackMask = pinMask;
elem->callback = callback;
/* Add element to the pin_callbacks list */
}
/* gpio_interrupt_callback is registered in the interrupt vector */
void gpio_interrupt_callback( void )
{
uint32_t pinMask = HWREG( GPIO_BASE + GPIO_O_EVFLAGS31_0 );
pin_callback_element_t *p_cp = pin_callbacks;
while(NULL != p_cp)
{
if(0 != (pinMask & p_cp->callbackMask))
p_cp->callback(pinMask);
p_cb = p_cb->next;
}
}
或d)
以上None
我知道中断回调必须尽可能快和小,但是我们现在制作的模块可以被其他应用程序使用,它们可能会或可能不会自己使用其他引脚中断,所以我认为根据其他应用程序更改通用代码是个坏主意
抱歉这么久 post 感谢您的反馈
我会选择 b),因为您 can/should 在 read-only look-up table 中注册了回调。这个 table 最好设置在 compile-time 并放置在你拥有所有其他 GPIO-related 常量的地方。
这样做的原因是 非常 烦人的是让项目的每个分散部分在内部与 GPIO 混在一起。您需要一个集中列表,您可以在其中概览哪些引脚是输入、输出、浮动、拉动等。这样您就可以轻松 review/verify 软件对照原理图,反之亦然。还要避免程序的不同部分意外使用相同引脚的冲突。
理论上,您可以创建一个动态read/write列表,如c),一个链表,不同的调用者可以在其中注册回调。我经常为 general-purpose 定时器创建这样的定时器,但在与硬件非常接近的 GPIO 的特定情况下,出于已经提到的原因,我建议不要这样做。
所以也许是这样的:
typedef void callback_t (void);
#define CALLBACK_N 32u
static callback_t* const callback [CALLBACK_N] = // function ptr table in read-only flash
{
[1] = callback_1,
[22] = callback_22
};
这意味着我们没有显式初始化的所有索引都将隐式设置为 null。
不幸的是,奇怪的 TI 库 return 是位掩码而不是位号,或者 ISR 中的 for 循环可能已被删除并替换为直接访问 look-up table.
一些未成年人nit-picks:
在 ISR 中可能会更改 if
语句的顺序以稍微加快执行速度(微优化):
if(pin_callback[i] != NULL) // check vs zero will be single instruction
if(pinMask & (1u << i))
始终u
后缀整型常量。永远做 1u <<
,永远不要做 1 <<
。如果您将数据转移到 1
创建的带符号 int
的符号位中,后者可能会调用未定义的行为。
不需要 1980 年代的“尤达条件”NULL != pin_callback[i]
废话。只需使用 half-decent 编译器,例如 1989 年或之后的 Turbo C - 它们会针对条件中的错误赋值发出警告。通过我上面的修复,函数指针 table 无论如何都是 read-only。
这里有点基于意见的问题...
我目前正在使用一个 uController(cc1310),它在单个回调中实现所有 32 个引脚的变化中断。
通过为给定的引脚启用中断,将调用所述回调,我们需要检查哪个引脚产生了中断,方法是:
/* gpio_interrupt_callback is registered in the interrupt vector */
void gpio_interrupt_callback( void )
{
uint32_t pinMask = HWREG( GPIO_BASE + GPIO_O_EVFLAGS31_0 );
if (pinMask & 0x00000001)
{
/* pin_0 generated interrupt */
}
...
}
此时pinMask
中的每一位都是对应的pin中断标志
直到现在,每个应用程序只需要一个 pin/process 来使用 ioc 事件,所以每次我们需要它时,我们只需注册我们的回调就可以了。
最后一个应用程序将使用更多,所以我正在考虑制作一个 GPIO_interrupt 模块,我可以在其中为给定的 pin 注册一个回调,该模块将通过 pinMask
和调用正确的回调。
所以...
大师们建议哪个选项?
a)
我只能为我使用的中断添加 if's
typedef void (*pin_interrupt_callback_t)( void );
pin_interrupt_callback_t pin0_callback;
/* gpio_interrupt_callback is registered in the interrupt vector */
void gpio_interrupt_callback( void )
{
uint32_t pinMask = HWREG( GPIO_BASE + GPIO_O_EVFLAGS31_0 );
if (pinMask & 0x00000001)
{
HWREG( GPIO_BASE + GPIO_O_EVFLAGS31_0 ) |= 0x00000001;
/* pin_0 generated interrupt */
if(NULL != pin0_callback)
pin0_callback();
}
else if (pinMask & 0x00000002)
{
...
}
...
}
b)
这样更动态一些?,我可以在其中添加或删除回调 to/from table (A table 也可以用在 a 中) 不过)
typedef void (*pin_interrupt_callback_t)( void );
pin_interrupt_callback_t pin_callback[32] = {
pin0_callback,
pin1_callback,
...
}
/* gpio_interrupt_callback is registered in the interrupt vector */
void gpio_interrupt_callback( void )
{
uint32_t pinMask = HWREG( GPIO_BASE + GPIO_O_EVFLAGS31_0 );
for(uint8_t i = 0; i < 32; i++)
{
if(pinMask & (1 << i))
if(NULL != pin_callback[i])
pin_callback[i]();
}
}
c)
这种方式是最复杂的一种,但我只会循环现有的中断,我可以为几个引脚注册相同的回调(也可以在a中这样做)和b),但这里如果同时发生多个中断只会调用一次)
我们甚至可以为每个引脚注册多个回调
typedef void (*pin_interrupt_callback_t)( uint32_t mask );
typedef struct pin_callback_element
{
uint32_t callbackMask;
pin_callback_element *next;
pin_interrupt_callback_t callback;
} pin_callback_element_t;
pin_callback_element_t *pin_callbacks = NULL;
/* elem must be declared as `static pin_callback_table_t elem;` */
uint8_t register_callback(
pin_callback_element_t *elem,
uint32_t pinMask,
pin_interrupt_callback_t callback )
{
/* Make sure no element is registered for a NULL callback */
elem->next = NULL;
elem->callbackMask = pinMask;
elem->callback = callback;
/* Add element to the pin_callbacks list */
}
/* gpio_interrupt_callback is registered in the interrupt vector */
void gpio_interrupt_callback( void )
{
uint32_t pinMask = HWREG( GPIO_BASE + GPIO_O_EVFLAGS31_0 );
pin_callback_element_t *p_cp = pin_callbacks;
while(NULL != p_cp)
{
if(0 != (pinMask & p_cp->callbackMask))
p_cp->callback(pinMask);
p_cb = p_cb->next;
}
}
或d)
以上None
我知道中断回调必须尽可能快和小,但是我们现在制作的模块可以被其他应用程序使用,它们可能会或可能不会自己使用其他引脚中断,所以我认为根据其他应用程序更改通用代码是个坏主意
抱歉这么久 post 感谢您的反馈
我会选择 b),因为您 can/should 在 read-only look-up table 中注册了回调。这个 table 最好设置在 compile-time 并放置在你拥有所有其他 GPIO-related 常量的地方。
这样做的原因是 非常 烦人的是让项目的每个分散部分在内部与 GPIO 混在一起。您需要一个集中列表,您可以在其中概览哪些引脚是输入、输出、浮动、拉动等。这样您就可以轻松 review/verify 软件对照原理图,反之亦然。还要避免程序的不同部分意外使用相同引脚的冲突。
理论上,您可以创建一个动态read/write列表,如c),一个链表,不同的调用者可以在其中注册回调。我经常为 general-purpose 定时器创建这样的定时器,但在与硬件非常接近的 GPIO 的特定情况下,出于已经提到的原因,我建议不要这样做。
所以也许是这样的:
typedef void callback_t (void);
#define CALLBACK_N 32u
static callback_t* const callback [CALLBACK_N] = // function ptr table in read-only flash
{
[1] = callback_1,
[22] = callback_22
};
这意味着我们没有显式初始化的所有索引都将隐式设置为 null。
不幸的是,奇怪的 TI 库 return 是位掩码而不是位号,或者 ISR 中的 for 循环可能已被删除并替换为直接访问 look-up table.
一些未成年人nit-picks:
在 ISR 中可能会更改
if
语句的顺序以稍微加快执行速度(微优化):if(pin_callback[i] != NULL) // check vs zero will be single instruction if(pinMask & (1u << i))
始终
u
后缀整型常量。永远做1u <<
,永远不要做1 <<
。如果您将数据转移到1
创建的带符号int
的符号位中,后者可能会调用未定义的行为。不需要 1980 年代的“尤达条件”
NULL != pin_callback[i]
废话。只需使用 half-decent 编译器,例如 1989 年或之后的 Turbo C - 它们会针对条件中的错误赋值发出警告。通过我上面的修复,函数指针 table 无论如何都是 read-only。