We all know the interrupts, but how do they really work?
The problem:
In my sketch an interrupt method (invoked when pin goes high) is consuming data from a buffer and the main loop()
is filling the buffer at random speeds (from Internet - I'm using ESP8266, not the Arduino). Although I think there shouldn't be any threading problems with this buffer - I might be wrong - the program hangs up after random number of seconds (about half a minute average - and then the built-in watchdog resets it after few seconds). When I detached the interrupt method and merged it into the main loop()
code, the problem disappeared (but I can't leave it like that). So it's probably either that I'm doing something illegal in the interrupt method (maybe it's just taking too long - how much time can an interrupt method take?) - or the second possibility - it is a threading problem.
The questions:
The main question is: how to correctly synchronize the interrupt method? How to create a critical section if the interrupt method cannot wait (for a semaphore) and if I leave it, it won't be invoked again because the pin is already high? And the detailed questions:
- How to create a thread-safe variable (a bool flag), so that interrupt method can skip the critical section if the main loop has entered it? (I understand the interrupt method is "safe" - it won't be interrupted - but how to secure the regular code?)
- If I detach an interrupt method that is triggered when a pin goes high when it is low (for just a few fast instructions), the pin gets high in that time, and then I'll re-attach the method to this pin again, will it be invoked? The same question for
noInterrupts();
How to make sure it will be invoked, if I can't check the pin and invoke (or not) the method manually in an atomic transaction with re-attaching the method? - If I detach this method (in main loop), is it possible that it will be invoked before the control is back from the
detachInterrupt()
method? (because the pin got high from external device in this very moment) And the same question fornoInterrupts();
- I've read that
noInterrupts();
causes some things, like reading incoming serial data, to be skipped. Can this method be even used if I don't want that? Or can I have guaranteed that it won't happen if the method is fast enough? (if so, please define the "fast enough"!)
So, is there a common template for creating a critical section between an interrupt method and the regular code? Or at least synchronizing a variable access? And if not, how to make sure the detached/blocked pin method will be invoked after re-attaching/re-enabling it?
The common buffer code:
(if you want to check whether this can cause any "threading" problems)
class Buffer
{
protected:
byte *buf;
int len, readpos, writepos;
bool wasFull, wasEmpty;
long ratioSum, ratioCount;
public:
Buffer(int length)
{
len = length;
buf = new byte[len];
readpos = writepos = 0;
}
bool isFull()
{
bool full = writepos - readpos >= len;
wasFull &= full;
return full;
}
bool isEmpty()
{
bool empty = readpos >= writepos;
wasEmpty &= empty;
return empty;
}
void write(byte b)
{
if (isFull()) { if (!wasFull) { wasFull = true; Serial.println("Error: Buffer overflow!"); } return; }
int pos = (writepos++) % len;
buf[pos] = b;
if (readpos >= len && writepos >= len) { readpos -= len; writepos -= len; }
}
byte read()
{
if (isEmpty()) { if (!wasEmpty) { wasEmpty = true; Serial.println("Error: Buffer underflow!"); } return 0; }
int pos = (readpos++) % len;
if (readpos >= len && writepos >= len) { readpos -= len; writepos -= len; }
return buf[pos];
}
int available()
{
return writepos - readpos;
}
void measureRatio()
{
ratioSum += available();
ratioCount++;
}
int getRatio()
{
int ratio;
if (ratioCount > 0)
ratio = 100 * ((float)(ratioSum / ratioCount) / (float)len);
else
ratio = 100 * ((float)available() / (float)len);
ratioSum = 0;
ratioCount = 0;
return ratio;
}
};
PS:
I have discovered that printing to Serial in the interrupt method (even without a flush()
is causing my program to crash after max half a second. Is it just a duration of printing or is it something else? Also if you're thinking that the warning printing in my Buffer code can cause the crashes, then I assure you that they're not because I always check isEmpty() and isFull() before each read and write operation, so they just can't occur.