Refresher on Mozzi Timing Before Tackling AS7341
I've decided to tackle the challenge of writing a Mozzi-friendly way to use an AS7341 sensor, using nonblocking I2C library twi_nonblock
. At a high level, this is a follow-up to my MMA7660 accelerometer Mozzi project several years ago. Due to lack of practice in the meantime I have forgotten much about Mozzi and need a quick refresher. Fortunately, Anatomy of a Mozzi sketch brought most of those memories back.
I connected a salvaged audio jack to the breadboard where I already had an Arduino Nano and my Adafruit AS7341 breakout board. (The AS7341 will sit idle while I refamiliarize myself with Mozzi before I try to integrate them.) After I confirmed the simple sine wave sketch generated an audible tone on my test earbuds, I started my first experiment.
I wanted to verify that I understood my timing constraints. I added three counters: the first is incremented whenever loop()
is called. The second when Mozzi calls the updateControl()
callback, and the third for updateAudio()
callback. Inside loop()
, I check millis()
to see if at least one second has passed. If it had, I print values of all three counters before resetting them back to zero. This test dumps out the number of times each of these callbacks occur every second.
loop 165027 updateControl 64 updateAudio 16401
loop 164860 updateControl 63 updateAudio 16384
loop 165027 updateControl 64 updateAudio 16401
loop 164859 updateControl 64 updateAudio 16384
loop 165027 updateControl 64 updateAudio 16401
loop 164860 updateControl 63 updateAudio 16384
loop 165028 updateControl 64 updateAudio 16400
loop 164859 updateControl 64 updateAudio 16385
loop 165027 updateControl 64 updateAudio 16400
loop 164858 updateControl 64 updateAudio 16384
loop 165029 updateControl 63 updateAudio 16401
loop 164858 updateControl 64 updateAudio 16384
loop 165027 updateControl 64 updateAudio 16401
loop 164858 updateControl 64 updateAudio 16384
loop 165029 updateControl 64 updateAudio 16400
loop 164860 updateControl 63 updateAudio 16384
Arduino framework calls back into loop()
as fast as it possibly can. In the case of this Mozzi Hello World, it is called roughly 165,000 times a second. This represents a maximum on loop()
frequency: as a sketch grows in complexity, this number can only drop lower.
In a Mozzi sketch, loop()
calls into Mozzi's audioHook()
, which will call the remaining two methods. From this experiment I see updateControl()
is called 63 or 64 times a second, which lines up with the default value of Mozzi's CONTROL_RATE
parameter. If a sketch needs to react more quickly to input, a Mozzi sketch can #define CONTROL_RATE
to a higher number. Mozzi documentation says it is optimal to use powers of two, so if the default 64 is too slow we can up it to 128, 256, 512, etc.
We can't dial it up too high, though, before we risk interfering with updateAudio()
. We need to ensure updateAudio()
is called whenever the state of audio output needs to be recalculated. Mozzi's default STANDARD mode runs at 16384Hz, which lines up with the number seen in this counter output. If we spend too much time in updateControl()
, or call it too often with a high CONTROL_RATE
, we'd miss regular update of updateAudio()
and those misses will cause audible glitches. While 16 times every millisecond is a very high rate of speed by human brain standards, a microcontroller can still do quite a lot of work in between calls as long as we plan our code correctly.
Part of a proper plan is to make sure we don't block execution waiting on something that takes too long. Unfortunately, Arduino's Wire library for I2C blocks code execution waiting for read operations to complete. This wait is typically on the order of single-digit number of milliseconds, which is fast enough for most purposes. But even a single millisecond of delay in updateControl()
means missing more than 16 calls to updateAudio()
. This is why we need to break up operations into a series of nonblocking calls: we need to get back to updateAudio()
between those steps during execution. Fortunately, during setup we can get away with blocking calls.