After some code tweaks, I’m now getting every single piece of data – yes, that’s right, I’m reading data at 1kHz!
Primary problem was integrating samples rather than averaging them. I’d found before that calling time.time() was actually a very time consuming call. By just adding samples instead of integrating them, I only need to call time.time() in each motion processing loop rather than per sample. It’s that which has taken my sampling rate to 1kHz – or to put it another way, the python code can now read the sensors faster than the sensors can churn out the data, so my code sits there twiddling its thumbs waiting for the data.
I’ve not tracked down the cause for the occassional I2C miss, so I’ve also moved the try / except wrapper from around the I2C read to around the (interrupt + I2C read). That forces the code to wait for the next sample before trying to read it again. That’s the cause for the spike in the middle.
Combining the above with my recent super-duper fast GPIO edge_detect_wait() update listening for a single 50us pulse per call, once more, I can climb back up on my trusty steed and blow raspberries at the RTOS dogma.
Code’s up on GitHub.
There are two threads in the code, one reading the sensors and once doing the motion processing. The sensor thread runs full speed, and the motion thread gets kicked at 71Hz to process the batch of data recorded since last time.
The sensor thread sits waiting for the data ready interrupt and then immediately reads the data over I2C. But what if the motion thread cuts in inbetween? It’s not as unlikely as it sounds because of the way the GIL works. If this happens, then the reading of the sensors is deferred longer than 1ms after the data-ready interrupt, and that means that duff data could be read or there may be an I2C error. That could well be the reason for the very long pulse shown in the ‘scope output.
I’d considered adding thread locks to the code when I introduced the threads, but decided it was unnecessary because the threads are completely independent except for the point where the sensor thread copies a batch of data from its own variables into a shared structure, and kicks the motion thread which copies it out from the shared structure. Because the motion thread is woken at 71Hz, but it’s processing takes only a few milliseconds, locking isn’t necessary.
But if my speculation is right, I do need some locking to prevent the motion thread being run in that tiny gap between interrupt and reading the sensors*. So that’s the next step.
I couldn’t resist flying her after all this testing even without the locking to see the effects of the other changes, and she just had two consistent flights. The first only had an I2C error in warm-up, and the flight was perfect. The second also had a I2C error mid-flight, at which point she started drifting slowly right, which backs up my theory.
I’m really quite excited at what effect the locking will show!
*There is still a risk the OS will get in the way at the point between the interrupt and reading the data, and this is where the necessity of a real-time OS steps in. At last I understand why DIY quadders are so dogmatic about needing Arduinos or a real-time OS but never being able to explain why in any solid way. My apologies for doubting your dogma.