C++ 使用C++;中断例程中的对象(和易失性)?
我目前与Atmel AVR微控制器(gcc)合作,但希望答案适用于微控制器领域,即通常为单线程,但有中断 我知道在访问可在ISR中修改的变量时,如何在C代码中使用C++ 使用C++;中断例程中的对象(和易失性)?,c++,interrupt,volatile,isr,C++,Interrupt,Volatile,Isr,我目前与Atmel AVR微控制器(gcc)合作,但希望答案适用于微控制器领域,即通常为单线程,但有中断 我知道在访问可在ISR中修改的变量时,如何在C代码中使用volatile。例如: uint8_t g_pushIndex = 0; volatile uint8_t g_popIndex = 0; uint8_t g_values[QUEUE_SIZE]; void waitForEmptyQueue() { bool isQueueEmpty = false; while
volatile
。例如:
uint8_t g_pushIndex = 0;
volatile uint8_t g_popIndex = 0;
uint8_t g_values[QUEUE_SIZE];
void waitForEmptyQueue()
{
bool isQueueEmpty = false;
while (!isQueueEmpty)
{
// Disable interrupts to ensure atomic access.
cli();
isQueueEmpty = (g_pushIndex == g_popIndex);
sei();
}
}
ISR(USART_UDRE_vect) // some interrupt routine
{
// Interrupts are disabled here.
if (g_pushIndex == g_popIndex)
{
usart::stopTransfer();
}
else
{
uint8_t value = g_values[g_popIndex++];
g_popIndex &= MASK;
usart::transmit(value);
}
}
由于g_popIndex在ISR内部修改,并在ISR外部访问,因此必须声明为volatile
,以指示编译器不要优化对该变量的内存访问。请注意,除非我弄错了,g_pushIndex
和g_值
不需要声明为volatile
,因为ISR不会修改它们
我想将与队列相关的代码封装在类中,以便可以重用:
class Queue
{
public:
Queue()
: m_pushIndex(0)
, m_popIndex(0)
{
}
inline bool isEmpty() const
{
return (m_pushIndex == m_popIndex);
}
inline uint8_t pop()
{
uint8_t value = m_values[m_popIndex++];
m_popIndex &= MASK;
return value;
}
// other useful functions here...
private:
uint8_t m_pushIndex;
uint8_t m_popIndex;
uint8_t m_values[QUEUE_SIZE];
};
Queue g_queue;
void waitForEmptyQueue()
{
bool isQueueEmpty = false;
while (!isQueueEmpty)
{
// Disable interrupts to ensure atomic access.
cli();
isQueueEmpty = g_queue.isEmpty();
sei();
}
}
ISR(USART_UDRE_vect) // some interrupt routine
{
// Interrupts are disabled here.
if (g_queue.isEmpty())
{
usart::stopTransfer();
}
else
{
usart::transmit(g_queue.pop());
}
}
上面的代码可以说更具可读性。但是,在这种情况下,应该如何处理volatile
1) 还需要吗?调用方法Queue::isEmpty()
是否以某种方式确保了对g_Queue.m_popIndex
的非优化访问,即使函数声明为inline
?我对此表示怀疑。我知道编译器使用试探法来确定访问是否应该优化,但我不喜欢依赖这种试探法作为一般解决方案
2) 我认为一个有效的解决方案是在类定义中声明成员Queue::m_popIndex
volatile
。但是,我不喜欢这个解决方案,因为类队列的设计者需要确切地知道如何使用它来知道哪个成员变量必须是volatile
。它不能很好地适应未来的代码更改。此外,所有队列
实例现在都将具有volatile
成员,即使其中一些未在ISR中使用
3) 如果将队列
类视为一个内置类,我认为自然的解决方案是将全局实例g_队列
本身声明为volatile
,因为它在ISR中修改,在ISR之外访问。但是,这并不能很好地工作,因为只有volatile
函数可以在volatile
对象上调用。突然间,队列
的所有成员函数都必须声明为volatile
(而不仅仅是常量
函数或ISR内部使用的函数)。同样地,队列
的设计者如何事先知道这一点?此外,这将惩罚所有队列用户。仍然有可能复制所有成员函数,并在类中同时具有volatile
和非volatile
重载,因此非volatile
用户不会受到惩罚。不漂亮
4) 队列
类可以模板化在策略类上,该策略类可以选择仅在需要时向其所有成员变量添加volatile
。同样,类设计者需要提前知道这一点,并且解决方案要理解起来更为复杂,但是哦,好吧
我很想知道我是否错过了一些更简单的解决办法。顺便说一句,我编译时还没有C++11/14支持。是的,内联是绝对需要的。
1) 编译器通常会在调用内联函数的每个位置放置一个新的内联函数副本。这种优化似乎不会影响可变变量。所以这没关系。
2) 我认为这是正确的解决方案(带有扩展)。因为唯一需要可变的变量实际上是队列索引。
3) 不,不需要将整个类实例标记为volatile,因为它可能会阻止其他潜在的优化。
4) 您可以使用继承。声明队列必须具有哪些函数的接口,以及两个继承类,一个用于ISR(队列索引为volatile),另一个用于不使用ISR。此外,您还可以始终定义您的类模板:
template<typename T>
class IQueue
{
public:
virtual bool isEmpty() const = 0;
virtual T pop() = 0;
protected:
uint8_t pushIndex;
T values[QUEUE_SIZE];
};
template<typename T>
class ISRQueue : public IQueue<T>
{
volatile uint8_t popIndex;
public:
inline bool isEmpty()const
{
return (pushIndex == popIndex);
}
inline T pop()
{
T value = values[popIndex++];
popIndex &= MASK;
return value;
}
};
template<typename T>
class Queue : public IQueue<T>
{
uint8_t popIndex;
public:
inline bool isEmpty()const
{
return (pushIndex == popIndex);
}
inline T pop()
{
T value = values[popIndex++];
popIndex &= MASK;
return value;
}
};
typedef ISRQueue<uint8_t> ISRQueueUInt;
typedef ISRQueue<uint8_t> QueueUInt;
模板
伊奎级
{
公众:
虚拟布尔isEmpty()常量=0;
虚拟T pop()=0;
受保护的:
uint8_t pushIndex;
T值[队列大小];
};
模板
类别:公共IQueue
{
易失性指数;
公众:
内联bool isEmpty()常量
{
返回(pushIndex==popIndex);
}
内联T pop()
{
T值=值[popIndex++];
popIndex&=掩码;
返回值;
}
};
模板
类队列:公共IQueue
{
uint8_t popIndex;
公众:
内联bool isEmpty()常量
{
返回(pushIndex==popIndex);
}
内联T pop()
{
T值=值[popIndex++];
popIndex&=掩码;
返回值;
}
};
typedef ISRQueue ISRQueueUInt;
typedef ISRQueue队列单元;
你的平台上有cli
和sei
内存障碍吗?@DavidSchwartz它们没有(我想——不是100%清楚)。我可以访问一个_MemoryBarrier()“指令”。@DavidSchwartz我更仔细地查看了文档,它们确实是内存障碍。