Hey folks, this discussion about using ATOMIC_BLOCK in AVR-specific code
touches upon another related subject, to which Mikael gives a possible
solution. Since this issue is really separate from (not) using
ATOMIC_BLOCK in AVR code, I've changed the subjct, so please try to
reply to this new subject only for comments to this.
Arduino has a portable interrupts() and noInterrupts() function, to
enable and disable interrupts. However, there is no portable "What is
the current interrupt state" function, so it is not currently possible
to disable interrupts, do something, and then *restore the previous
state*. You can only *enable* the interrupts afterwards, which obviously
causes all kinds of problems if interrupts were previously disabled.
This is mostly an issue for libraries and I guess libraries are not
often called with interrupts disabled, but I'm still surprised this
issue has not come up before.
So, I believe Arduino should have some way to get the current interrupt
state, and to conditionally enable interrupts based on the previous
state.
Mikael shows that Cosa has a lock() / unlock() function for this. Though
I don't really like the name "lock" (which is usually used for other
concepts in concurrent programming), the way these functions work seems
useful, so I'll just stick to these names in this discussion
(suggestions for better names welcome!)
Our current `noInterrupts()` function can be made to work like `lock()`
by letting it return the current interrupt state. A downside is that
when the return value is not used, this will add a bit of overhead
(since, at least on AVR, the read of SREG is volatile and cannot be
optimized away).
Making `interrupts()` work like `unlock()` is more tricky, since that
requires adding a parameter, breaking existing code. Converting it to an
inline function with an optional parameter could work, but that breaks C
code (only allowing C++). Adding a new function/macro that takes an
argument to exist alongside `interrupts()` is probably better, then.
The return type of the `lock()` function proposed by Mikael is
`uint8_t`, but is probably good to add a typedef for that, like
`interrupt_state_t`, in case other architectures need a different type
of this flag. Alternatively you could use a bool, but (on AVR) that
requires a bit more code to save and restore the flag and there could be
architectures that need > 1 bit of data to store.
> #define synchronized for (uint8_t __key = lock(), i = 1; i != 0; i--,
> unlock(__key))
Having a portable block like this really seems worthwile to me. It's
basically just a portable version of ATOMIC_BLOCK.
> class Lock {
A Lock variable like this, using a destructor to restore interrupt state
is also a nice thing to have. One might wonder if having three different
ways to achieve the same is a bit too much, though each of these three
will be useful in different contexts.
> Define a Lock variable in a block and the code after the variable in the
> block is atomic. This handles both goto's and return statements something
> that the macro above does not without some additional help. See the linked
> files for more details. There are plenty of examples of the resulting style
> in the Cosa source code.
FWIW, I think the ATOMIC_BLOCK macro _does_ handle gotos and returns,
becauses it uses the cleanup attribute (basically a gcc-extension to add
a "destructor" to an arbitrary variable), so you might want to consider
this one as well.
Gr.
Matthijs