MOCmaniac
Member
Hello everyone,
As part of my thesis on electric bikes, I’m using a Teensy 4.1 (and arduino IDE on windows) to collect and process data from a large brushless motor controlled by an ODrive Pro. Communication with the ODrive and other components is done via CAN, and this setup cannot be changed.
I log 15 float values along with a timestamp to an SD card at 1 kHz, using CSV format. I know binary logging would be more efficient, but I’ve already achieved up to 20 kHz in CSV without issues, so I currently have enough performance margin. In parallel, I apply an Exponential Moving Average (EMA) filter to each of the 15 values, each with its own cutoff frequency.
So far, everything is working as expected: CAN communication, logging, filtering, and general data handling are functional. However, the timing between consecutive log entries or filter applications is not as regular as I would like. The average loop execution time is around 300 to 600 µs, but there is noticeable jitter in the actual timing of logs and filter updates.
To improve timing consistency, I recently moved the filtering to a timer interrupt. This makes the filter application more regular, but introduces a new problem: accessing and modifying shared data between the main loop, CAN reception, and the timer interrupt. I am currently disabling interrupts whenever I copy or update shared values to avoid race conditions, but this approach quickly becomes complex and hard to maintain, especially as the codebase grows.
I used an array of pointers to the float variables I wanted to log and filter (dataPtr). I also have a array with the headers, with the cut-off frequency and associated alphas. This made it easy to loop through the data for both operations. To improve structure and flexibility, I recently thought about creating a struct for each variable, like this
All the variables would be stored in an array of these structs, which I can iterate over for filtering and logging.
This setup is clean in theory, but I'm still facing the core issue: how to manage shared access to these values across interrupts and the main loop, without having to disable and enable interrupts constantly or having data duplicates everywhere. I also plan to move the logger into a timer interrupt, which will add further pressure on data access safety.
I’m looking for advice or examples of best practices to manage shared float variables between the main loop, CAN handlers, and multiple interrupts, in a way that remains efficient, safe, and maintainable. I suspect there are better approaches or patterns for handling this kind of situation that I’m not aware of. You can find the code linked to this problem in the attached files.
There are probably other things I could improve in the code, but for now it works. Once I have a solution to this problem, I can start improving the code, and any advice would be welcome for this part as well!
Thanks in advance for your help,
Arnaud
As part of my thesis on electric bikes, I’m using a Teensy 4.1 (and arduino IDE on windows) to collect and process data from a large brushless motor controlled by an ODrive Pro. Communication with the ODrive and other components is done via CAN, and this setup cannot be changed.
I log 15 float values along with a timestamp to an SD card at 1 kHz, using CSV format. I know binary logging would be more efficient, but I’ve already achieved up to 20 kHz in CSV without issues, so I currently have enough performance margin. In parallel, I apply an Exponential Moving Average (EMA) filter to each of the 15 values, each with its own cutoff frequency.
So far, everything is working as expected: CAN communication, logging, filtering, and general data handling are functional. However, the timing between consecutive log entries or filter applications is not as regular as I would like. The average loop execution time is around 300 to 600 µs, but there is noticeable jitter in the actual timing of logs and filter updates.
To improve timing consistency, I recently moved the filtering to a timer interrupt. This makes the filter application more regular, but introduces a new problem: accessing and modifying shared data between the main loop, CAN reception, and the timer interrupt. I am currently disabling interrupts whenever I copy or update shared values to avoid race conditions, but this approach quickly becomes complex and hard to maintain, especially as the codebase grows.
I used an array of pointers to the float variables I wanted to log and filter (dataPtr). I also have a array with the headers, with the cut-off frequency and associated alphas. This made it easy to loop through the data for both operations. To improve structure and flexibility, I recently thought about creating a struct for each variable, like this
C++:
struct dataStruct {
const char* header; // Used in the CSV
float rawvalue; // Usually updated by the CAN receive function
float filteredValue; // Calculated with the EMA
unsigned long cutOffFreq;
float alpha;
};
All the variables would be stored in an array of these structs, which I can iterate over for filtering and logging.
This setup is clean in theory, but I'm still facing the core issue: how to manage shared access to these values across interrupts and the main loop, without having to disable and enable interrupts constantly or having data duplicates everywhere. I also plan to move the logger into a timer interrupt, which will add further pressure on data access safety.
I’m looking for advice or examples of best practices to manage shared float variables between the main loop, CAN handlers, and multiple interrupts, in a way that remains efficient, safe, and maintainable. I suspect there are better approaches or patterns for handling this kind of situation that I’m not aware of. You can find the code linked to this problem in the attached files.
There are probably other things I could improve in the code, but for now it works. Once I have a solution to this problem, I can start improving the code, and any advice would be welcome for this part as well!
Thanks in advance for your help,
Arnaud