I have some code that I now wish to run inside a timer-based interrupt on a Teensy 3.6 microcontroller. The code accesses a [global] array of objects of a class. I have marked that array and all of the member variables as volatile, which I believe is the first step for correctly dealing with interrupts.
One of the member variables which I have marked volatile is an std::bitset and I would like to call it's non-volatile methods, which I can't do as
"passing 'volatile std::bitset<16u>' as 'this' argument discards qualifiers [-fpermissive]"
I think I could just duplicate the bitset library and switch everything to volatile, but I don't think that should be required, so I think there is either a better solution, or I am thinking about things incorrectly.
Please let me know what should be done.
These answers seem to recommend the use of volatile when accessing global variables in an ISR: C 'Volatile' keyword in ISR and multithreaded program?,
What is the correct way of using C++ objects (and volatile) inside interrupt routines?,
Is volatile needed when variable is only read during interrupt
volatile keyword usage in ISR function in micro-controller programing
This is in addition to many external sources recommending the use. Maybe my original message was not clear or maybe my situation differs from these.
You should not be setting everything to volatile. Volatile has a specific purpose, and that's to prevent the compiler from optimizing out reads and writes to memory. Let's look at a really simple example.
int regular_sum(int* ptr) {
int a = *ptr;
int b = *ptr;
return a + b;
}
int volatile_sum(int volatile* ptr) {
int a = *ptr;
int b = *ptr;
return a + b;
}
When we look at the assembly, we see that in regular_sum
, the compiler realizes you're dereferencing the same pointer twice, and it optimizes it to just one dereference. But in volatile_sum
, the compiler inserts both dereferences:
regular_sum(int*):
mov eax, DWORD PTR [rdi]
add eax, eax
ret
volatile_sum(int volatile*):
mov eax, DWORD PTR [rdi]
mov edx, DWORD PTR [rdi]
add eax, edx
ret
Optimizations are good, and most of the time, you won't need to use volatile. If you're doing memory-mapped IO, or you're writing values to pins as though they were a pointer, that's where you use volatile. To reiterate what Nathan Oliver said,
You only need to use volatile on variables where the hardware can change the value of the variable because the compiler can't know about that. This is what volatile is for, letting the compiler know this is a special variable that could be changed in a manner it doesn't know about. If the hardware cant change the value on you, then you don't need volatile.
But if you're doing computations on an object, don't use volatile. Do the computations on a normal object, and then copy the result to your volatile pointer.
Volatile and Interrupt Service Routines.
It is appropriate to use volatile
on global variables that might be modified by Interrupt Service Routines. That being said, volatile
cannot be used with objects like std::bitset
because std::bitset
does not have support for volatile operations, and std::bitset
is not trivially copyable.
In this regard, you have two options:
std::vector<volatile bool>
If you have a class that is trivially copyable, then you can do something like the following. First, we have to define functions to allow us to copy to and from volatile types:
template<class T>
T volatile_copy(T const volatile& source) {
static_assert(std::is_trivially_copyable_v<T>, "Input must be trivially copyable");
T dest;
auto* dest_ptr = dynamic_cast<char*>(&dest);
auto* source_ptr = dynamic_cast<char const volatile*>(&source);
for(int i = 0; i < sizeof(T); i++) {
dest_ptr[i] = source_ptr[i];
}
return dest;
}
template<class T>
void volatile_assign(T volatile& dest, T const& source) {
static_assert(std::is_trivially_copyable_v<T>, "Input must be trivially copyable");
auto* source_ptr = dynamic_cast<char*>(&source);
auto* dest_ptr = dynamic_cast<char volatile*>(&dest);
for(int i = 0; i < sizeof(T); i++) {
dest_ptr[i] = source_ptr[i];
}
}
Then, we may write a class normally, and as long as it's trivially copyable we can create a copy from a volatile version:
struct MyBitset {
uint64_t bits;
// Logic
void flip() {
bits = ~bits;
}
void addOne() {
bits++;
}
};
volatile MyBitset flags;
void interrupt_handler() {
auto local = volatile_copy(flags);
// Do stuff to local
volatile_assign(flags, local);
};
We can also encapsulate this behavior in a class, so that we can "check out" volatile variables:
template<class T>
struct Checkout {
T local;
T volatile& source;
Checkout(T volatile& source)
: local(volatile_copy(source))
, source(source) {}
void save() {
volatile_assign(source, local);
}
~Checkout() {
save();
}
};
Using this allows us to create local copies of volatile
variables, make modifications to them, and the result will be saved automatically:
volatile MyBitset flags;
void interrupt_handler() {
auto f = Checkout(::flags);
f.local.flip(); //We can call whatever member functions we want on the local
// When the function exits, changes made to the local are automatically assigned to the volatile global
}