为了澄清问题,假设我们有:
- 静态按钮对象:
static Button_T sButton = {0};
- 获取按钮的函数:
void GetButton(Button_T * p_button);
,从主循环上下文调用 - ISR 处理程序:
void ButtonISRHandler(void);
假设:
GetButton
执行可以被任何不执行ButtonISRHandler
的中断中断ButtonISRHandler
执行可以被其他迭代中断GetButton
执行所需的时间少于两个ButtonISRHandler
中断调用之间的最短时间。- 按钮中断是一个循环中断,例如每 10 毫秒触发一次。
- 在
ButtonISRHandler
中,我们有一些程序,例如检查按钮 PIN 状态或检测按钮是否被触摸(在触摸按钮情况下)。如果给定的 PIN 状态在例如对于 5 个连续调用,sButton
对象状态会更新。 Button_T
是通用对象 - 它可以是经典的轻触开关或触摸按钮等。ScanButtonAndUpdate
可以处理一系列 Button_T 对象,但GetButton
函数只能对单个按钮对象进行操作。
问题是:当程序计数器位于GetButton
内部时可能发生中断的经典情况
问题是:如何在不禁用中断的情况下将 GetButton
与 ButtonISRHandler
同步?
我的目标处理器是 Cortex M0,没有 LDREX/STREX 操作,因此我无法使用 C11 中的原子,这在这种情况下是一个很好的解决方案。
我建议的解决方案
在GetButton
中使用关键部分。
如果当程序计数器位于关键部分内时发生中断,则不要在中断中处理 ScanButtonAndUpdate
,而是在 ExitCriticalSection
上处理它。推迟 ScanButtonAndUpdate
执行。
不可能同时从中断和主上下文调用 ScanButtonAndUpdate
函数 - 此行为受信号量保护
实现
#define SEMAPHORE_GIVEN 0
#define SEMAPHORE_TAKEN 1
typedef uint32_t BaseType_T;
typedef struct Button_T;
static volatile BaseType_T sSemaphore = SEMAPHORE_GIVEN;
static volatile bool sIsPendingISR = false;
static volatile Button_T sButton = {0};
void GetButton(Button_T * p_button)
{
EnterCriticalSection();
memcpy(p_button, &sButton, sizeof(Button_T))
/* Other procedures on sButton... */
ExitCriticalSection();
}
/* Cyclic executed handler */
void ButtonISRHandler(void)
{
if (!BinarySemaphoreTake()) {
SetISRPending();
}
else {
ScanButtonAndUpdate();
BinarySemaphoreGive();
}
}
void ScanButtonAndUpdate(void)
{
/* Scan for instance a current PIN state and update sButton object
if state is stable in next calls */
}
static void EnterCriticalSection(void)
{
while(false == BinarySemaphoreTake()) continue;
}
static void ExitCriticalSection(void)
{
BinarySemaphoreGive();
if (IsPendingISR()){
ScanButtonAndUpdate();
ResetISRPending();
}
}
static bool BinarySemaphoreTake(void)
{
if (SEMAPHORE_GIVEN == sSemaphore) {
/* Value Store operation is atomic on the architecture native type */
sSemaphore = SEMAPHORE_TAKEN;
return true;
}
else {
return false;
}
}
static void BinarySemaphoreGive(void)
{
sSemaphore = SEMAPHORE_GIVEN;
}
static void SetISRPending(void)
{
sIsPendingISR = true;
}
static void ResetISRPending(void)
{
sIsPendingISR = false;
}
static bool IsPendingISR(void)
{
return sIsPendingISR;
}
该解决方案经过测试,运行良好,没有任何问题,但我不确定这是没有隐藏错误的最佳解决方案。
编辑 1:更新了假设并添加了缺少的 ScanButtonAndUpdate
函数
最佳答案
有一个隐藏的同步会影响您是否有竞争条件:什么门控中断?两种最常见的场景是边缘触发和电平触发;边沿触发意味着中断将被禁止,直到设备被清除;而电平触发意味着中断将重复重新置位,直到设备被清除。
如果您的代码使用电平触发中断,则您完全省略了此同步,或者您假装 sIsPendingISR 是掩码和状态标志。那样的话,你看起来还不错
如果它是电平触发的,那么它可以在/*更新sButton对象*/期间重新断言,导致设备处理代码在两个上下文中执行(中断+正常)。大多数设备代码并非旨在执行此操作。
顺便说一句,有一个名为“Dekkers Algorithm”的软件协议(protocol),它提供了一种无需硬件支持的互斥通用解决方案。您已经在这里集成了它的一个版本。
关于c - Cortex M0 上无需禁用中断的同步机制,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55003417/