热度 19
2014-8-8 14:55
1731 次阅读|
0 个评论
Good grief! I wonder where time goes. It seems like only a couple of days ago that I was waffling on about my MSGEQ7-Based DIY Audio Spectrum Analyser . However, the sands of time have wended their way through the hourglass, as is their wont; the days have turned into weeks; and thus we find ourselves in the "here-and-now." What can I say? I'm too young for all of this excitement (LOL). As usual, just to set the scene before we leap headfirst into the fray with gusto and abandon, let's first take a look at this short video to remind ourselves as to just how cool this looks in the real world. Now, in this column, I'd like to share a few extra tidbits of trivia and nuggets of knowledge that I've picked up along the way. With regard to accessing the spectrum data from the MSGEQ7s, let’s start by reminding ourselves of the official timing requirements from the data sheet, as illustrated below. As we see, a positive-going pulse on the RESET signal kicks everything off. This pulse takes a copy of the current peak detector outputs and stores (latches) the values. We then apply seven negative-going pulses to the STROBE input. Every time the STROBE input goes low, we can read the value of one of the bands on the DATA_OUT signal, starting with 63 Hz and working our to 16,000 Hz. The DATA_OUT is an analog value whose magnitude reflects the value from the corresponding peak detector. This value can be read using one of your microcontroller's analog inputs. (Observe that the DATA_OUT signal is clamped to 0V when the STROBE signal is HIGH.) When I originally purchased the MSGEQ7 chips, I looked around on the Internet to see if there was any sample code to start me on my way. In fact, I found quite a few different examples, each doing things in slightly different ways, but a representative version of the function used to read the spectrum data values from the MSGEQ7s might be as shown below. Note that the names I use for the RESET and STROBE signals in my code are "ctrlReset" and "ctrlStrobe," respectively. As we see, we start with a positive-going pulse on the ctrlReset signal, and then we enter a loop to read the seven data values. Observe that the only delay that is explicitly defined occurs after the ctrlStrobe signal is driven LOW. According to the data sheet, the minimum output settling time is 36 µs, and some of the examples I found used this exact value, but in my code I boosted this up to 40 µs, just to "make sure." To be honest I feel like an old fool (but where are we going to find one at this time of the day? LOL). I know better than to simply use someone else's code without thinking about it, but that's what I did, and it worked, so I might have simply left it at that, but... ...as I was driving into work one morning, I suddenly got a "niggly feeling" about the width of the positive-going pulse on the ctrlReset line. From the data sheet, we know that this pulse has a minimum width of 100 ns. It struck me that all of the code examples I'd seen had been for an 8-bit Arduino Uno or Arduino Mega running at 12 MHz, but I'm running this on a 32-bit chipKIT MAX32 running at 80 MHz. Could it be that my pulse was too narrow? I started off under the misguided assumption that the compiler would optimize and/or convert the "digitalWrite (ctrlReset, HIGH);" and "digitalWrite (ctrlReset, LOW);" function calls into low-level instructions that would be executed in a single clock cycle. On this basis, the width of the positive-going pulse on the ctrlReset signal on my chipKIT would be 1/80,000,000 = 12.5 ns, which would be way too narrow. Even on an Arduino, this pulse width would be 1/12,000,000 = 83 ns, which would still be too narrow. But my system was doggedly working, so what was going on? In order to see what was happening in the real world, we hooked everything up to an oscilloscope. The results were as illustrated below: Imagine my surprise to discover that the positive-going pulse width of the ctrlReset signal on my chipKIT was actually 800 ns. What this means is that "digitalWrite (ctrlReset, HIGH);" and "digitalWrite (ctrlReset, LOW);" function calls aren’t being optimized into low-level instructions. Instead, they are being executed as full-blown function calls, which involve things like pushing stuff onto the stack. This means that each of these high-level statements is actually consuming 800ns/12.5ns = 64 instructions. On the one hand I thought "Wow! That is really inefficient." On the other hand, I thought, "Well, at least my pulse is wide enough." But now look at the reset-to-strobe (trs) time. From the data sheet, this has a minimum value of 72 µs, but the measured value is only 800 ns, which is only approximately 1% of the required value. The MSGEQ7 is obviously a fairly robust little chappie; in fact, it's amazing that it worked at all. Even though it did work, however, I decided to add the appropriate delay as illustrated below (once again, I added a couple of microseconds for safety's sake). At 49 µs, the low-going ctrlStrobe pulse width (ts) exceeds the minimum specified value of 36 µs. But look at the narrow high-going pulses on the ctrlStrobe signal. Since the minimum strobe-to-strobe time is specified as 72 µs, these high-going pulses should have a minimum value of 72 - 49 = 23 µs. However, their actual width was only 800 ns (the delay associated with the combination of the "digitalWrite (ctrlStrobe, HIGH);" statement at the end of the loop followed by the "digitalWrite (ctrlStrobe, LOW);" statement at the Beginning of the loop), which is only around 3.5% of the desired value. Once again, even though the original code did work, I decided to add another delay as illustrated below (as usual, I boosted this up somewhat to give myself some "breathing room"). The final code for the "readMSGEQ7s()" function I'm currently using is shown below. You will observe that this includes a simple noise filter in the form of the two instructions highlighted in yellow. The thing is that even when no music is playing, you tend to get low-level noise when you read the DATA-OUT signal. In turn, this causes the LEDs to flicker, which you don’t really want between tracks. I played around a little and determined that removing anything below a value of 50 worked rather well, but then I changed this to a value of 42 (the answer to "life, the universe and everything") on a whim. Last, but not least, speaking about noise, I was looking at some code created by my chum Steve Manley for use with his MSGEQ7-based system when I noticed that he'd read his DATA_OUT value twice and averaged the two values. In the context of my code, and just considering the left channel, this would look something like the following: When I asked Steve why he'd done this, he said that he'd heard that this was a useful technique to mitigate against unwanted noise on the DATA_OUT signal. In fact, Steve said he's actually seen people averaging four samples (as shown below) and even eight samples (which seems to be overly enthusiastic, if you ask me). Since I'm always "game for a laugh," I ran a few experiments comparing the results from using one, two, and four samples. I looked at both the LED displays and the corresponding numerical values -- and I really couldn’t see much difference, so I returned to using a single sample. However, I would be very interested to hear your thoughts on this. Have you seen this technique being used or used it yourself? Are there any situations when one really should make use of multiple samples in this manner? As always, I welcome any comments and questions.