Performing AS7341 Sensor Integration in Parallel Resolved Mozzi Glitches
I've set up an ESP32 development board for interfacing with both an AS7341 spectral color sensor as well as Mozzi audio generation libraries. This is a follow-up to my project doing the same with an AVR ATmega328-based Arduino Nano. On those AVR boards, trying to compile Mozzi with Adafruit's AS7341 library would fail due to conflict between Mozzi's twi_nonblock
I2C library and Arduino's Wire I2C library.
[...]\libraries\Mozzi-master\twi_nonblock.cpp.o (symbol from plugin): In function `initialize_twi_nonblock()':
(.text+0x0): multiple definition of `__vector_24'
[...]\libraries\Wire\utility\twi.c.o (symbol from plugin):(.text+0x0): first defined here
collect2.exe: error: ld returned 1 exit status
exit status 1
Compilation error: exit status 1
My earlier project solved that problem by avoiding Arduino Wire library, writing AS7341 interface code with twi_nonblock
while using Adafruit's library as a guide. (But unable to copy code straight from it.) However, twi_nonblock
is excluded from non-AVR platforms like ESP32. The good news is that we avoid above compiler error, the bad news is that we're on our own to find a non-blocking I2C API. For an ESP32, that meant dropping down to Espressif's ESP-IDF I2C API. I believe that's within my coding capabilities, but there was an easier way.
Why does as7341.readAllChannels()
consume so much time that it causes Mozzi audio glitches? Arduino Wire's blocking I2C operations are certainly a part of that process, but most of that time is spent in AS7341 sensor integration. as7341.readAllChannels()
starts an integration and waits for it to complete before returning results, blocking our Arduino sketch the entire time. But Adafruit foresaw this problem and included a provision in their AS7341 library: we can start an integration without blocking on it. Our sketch resumes code execution, allowing Mozzi to update audio while integration occurs in parallel. Once integration is complete, we can retrieve the values and do all the same things we would do for results of as7341.readAllChannels()
.
This concept was illustrated in the Adafruit example sketch reading_while_looping
, which I thought was promising when I reviewed all example sketches earlier. I couldn't try it on an AVR due to Wire/twi_nonblock compiler conflict, but I could give it a shot on this ESP32. I started with the reading_while_looping
example sketch and converted it over to a Mozzi sketch. First by moving existing code in loop()
into updateControl()
, leaving just a call to audioHook()
inside loop()
. For my first test I didn't need anything fancy, so I just had Mozzi play a steady 440Hz sine wave in updateAudio()
. (A440 is the note used by western classical music orchestra to verify all instruments are in tune with each other.)
The first run was a disaster! Audio glitches all over the place, but I knew there was room for improvement. There was an entirely unnecessary delay(500)
, which I deleted. Interleaved with the parallel integration is a blocking integration to our old friend as7341.readAllChannels()
. I don't understand why the blocking code is in the middle of a non-blocking example, but I deleted that, too. This removed most of the problems and left a little recurring audible click. Looking over what's left, I noticed this sketch made quite a number of calls to Serial.println()
. After their removal I no longer heard glitches in Mozzi audio.
I2C communication is still performed with Arduino Wire library. But this experiment empirically showed the communication is fast enough on an ESP32 that Mozzi does not audibly glitch despite Wire's blocking nature. This is much easier than dropping down to ESP-IDF I2C API. Also, this approach portable to other non-AVR Mozzi platforms like Teensy.
After this successful experiment, I modified one of Emily's color organ sketches and the resulting pull request shows the changes I had to make. They were quite minimal compared to rewriting everything with twi_nonblock
.
Playing with Mozzi was a fun challenge catering to its timing requirements. But as I proceed to play with AS7341, I'd prefer to shed Mozzi timing constraint and focus on other capabilities of this sensor.
Code for this exploration is publicly available on GitHub