Spectrum Analyzer Revisited

Encouraged by the happy ending of the Swinging Needles project, I decided to revisit an earlier audio spectrum analyzer. This time, instead of programmatically processing analog signals, I used a VS1053 plugin to let the chip do the Fourier stuff.

Before even starting to read actual frequency band values, I wanted to test my sketch by reading the plugin’s default frequency settings from VS1053’s memory. However, some of these readings made no sense at all, up to the point that I suspected a faulty chip. It took me some time to find out that the chip needs a few seconds of audio input before it will tell you something useful, even if it concerns a fixed setting like number of frequency bands.

Once the above mystery was solved, things became very straightforward. What a versatile chip this VS1053 is! While playing a 320 Kbps internet radio station, it can easily handle 14 frequency bands and let an esp8266 at 160 MHz show the results on a 128×64 Oled display. Part of the credits should go to the authors of the libraries that I used.

 

First impression produced by the sketch below; better videos with sound (and colors?) will follow.

 

Here’s an esp8266 demo sketch for a basic internet radio (fixed station, no metadata, no audio buffer), just to show a 14 band spectrum analyzer on a 128×64 SH1106 Oled display. Used pins are for the rather rare Wemos D1 R1, so you’ll probably have to change them.

 

This is the content of the plugin.h file that needs to be in the directory of the sketch:

 

 

 

 

Swinging Needles

…finally!

It took me some time (and strong coffee), but I finally managed to have the ESP32 version of my Internet Radio drive two vintage style analog VU meters!

 

And here they are…

Web browser controlled prototype with basic display and no matrix keypad attached

Soon after I had purchased a rather obscure VU meter kit, it turned out that the driver board was completely useless because it was mono (Chinese for stereo?).

So I decided to have the ESP32 read the analog (DC-biased) line out signals from the VS1053 mp3 decoder, map the peak-to-peak readings to [0,255] scaled values and send these to the ESP’s DACs for driving the meters. However, I couldn’t figure out how to do that without having to connect the VS1053’s floating audio ground to the system ground, which, so I guessed, would either not work, cause noise or even damage something.

Then I remembered the VU meter plugin for the VS1053, enabling you to read dB levels from a memory register. But these plugin readings are leaking peak dB levels, so I started wondering if it was possible to use them for reconstructing instantaneous dB levels. Not suitable for audio purists, but I wouldn’t mind if the result was convincing. After all, these cheap Chinese meters will never behave like professional VU meters anyway.

So I wrote a simple algorithm with some tuning parameters and functions for scaling and smoothing, calculated resistor values for limiting current to my meters to 0.4 mA max, added (probably unnecessary) flyback diodes to protect the ESP32 from negative voltage spikes, and just gave it a try. Here’s the wiring that I used for the meters.

The first result looked surprisingly good: the needles eagerly followed every move and twist of the music. Since everything is controlled by an algorithm, any desired meter ballistics can be easily simulated without electronics.

For now, further fine tuning seems unnecessary. Besides, a more audiophile approach already crossed my mind: have a FreeRTOS task listen in to the mp3 stream while it’s being sent to the VS1053, and programmatically decode it. That will allow me to extract true dB levels and could even become a replacement for the VS1053 board. Like I recently read in an article about Software Defined Radio (SDR), “software is the new hardware“.

Anyway, music finally looks almost as good again as it did in my youth. Let’s just hope for an analog VU meter revival soon. And how about a global rediscovery of reel-to-reel tapedecks (with analog VU meters, of course)? That would make much more sense to me than the somewhat puzzling comeback of vinyl.

Mini MP3 Player

This little €4 module filled my entire weekend, mainly spent on finding the right documentation and a library that will work on Arduino, ESP8266 and ESP32. It’s a DFPlayer Mini MP3 Player from DFRobot (or, more likely, one of its many clones).

It can play MP3 and WAV files, stored on an SD card in the onboard card reader or from an attached USB drive. So far, I only tested MP3 files from SD.

You can connect one (4 Ω) speaker to the (quite impressive) onboard mono amplifier. Stereo line output is said to be provided on the DAC_R and DAC_L pins.

The main control method of this player is via commands over the module’s 9600 baud serial port. A subset of most commonly used commands can also be executed by means of two GPIO pins and/or two resistor-sensitive ‘ADKEY’ pins.

I made a false start by testing it on an ESP32 over hardware serial. That didn’t seem to work, so I tried an ESP8266 over software serial. That didn’t work either, and I had reached the point of giving up, assuming it was just another Chinese fake, when I gave it one more try on my good old Arduino Uno. With a quick & dirty 1K resistor for level shifting the module’s RX pin, the example sketch of one of many libraries finally made the little guy play me a song! In the end, it was the power supply that made the difference.

Some important things that I learned so far:

  • The module works best when powered by a stable 5 Volt. My Arduino Uno and Wemos D1 R2 could deliver it from their 5V pin, smaller ESP8266 and ESP32 boards couldn’t. In that case: use a separate 5 Volt power supply.
  • Wiring as suggested here caused a shutdown of my laptop’s usb port! I’m still searching for a way to connect the module to ESP32.
  • The module operates on 3.3 V logic level, so make sure to level shift the connection to the module’s RX pin when using a 5 V microcontoller.
  • Startup noise disappeared after I connected both GND pins from the player to ground.
  • Although it’s for a Flyron FN-M16P module, this excellent manual/datasheet has more and better written information than DFRobot’s own poorly translated summary. Apparently both modules use the same commands and probably have the same chip.
  • Getting stereo ouput from the DAC pins is still a problem. According to the manual, DAC is enabled on startup, but is it? Explicitely enabling it by sending 0x0000 to memory address 0x1A made no difference while sending 0x0001 instead (supposed to turn it off) started a terrible noise…
  • I tested quite some libraries from github, most of which have issues [update: starting sketches with delay(1000); solved most of them] and seem unnecessarily complicated to me. An exception is this simple and transparent library that is basically just a wrapper for sending serial commands. It can easily be extended with additional methods, even by unexperienced C++ programmers. You just have to lookup the command- and parameter bytes for the desired function in the datasheet and create a new function to send them (see example at the bottom of this page). [update: the author of the library has meanwhile added more commands]
  • The module has an SPK_1 and an SPK_2 pin, with a GND pin in between, which could suggest that it has a stereo amplifier. Although the documentation clearly says it’s mono, someone actually published a wiring diagram with two speakers connected. This person may own a different version of the module, but mine definitely has a mono amplifier. So the two wires of a single speaker should go to the SPK pins.

[ Wiring for Arduino Uno, with SoftSerial on pins 10 and 11; don’t forget the 1K resistor! ]

 

The location of files and their names is also very important because of the way commands address them. All information can be found in the datasheet, but here’s a summary:

  • The SD card’s maximum size is 32 GB. It must be FAT or FAT32 formatted.
  • There can be up to 3000 .mp3 (or .wav) files in the root directory of the SD card, named 0001.mp3 to 3000.mp3.
  • The root can have up to 99 subdirectories, named 01, 02, … 99. Each of these subdirectories can hold up to 255 files. Their names must start with 001, 002, … 255,  but you can append decriptive titles to these files, e.g. 007_James_Bond_Tune.mp3.
  • You can have a special subdirectory in the root named MP3. This can hold up to 3000 files, named 0001.mp3 to 3000.mp3.
  • You can also have a subdirecory ADVERT in the root. Special commands are available for making tracks in this folder interrupt currently playing tracks from the MP3 folder (for advertisement puposes). The rules for the MP3 subdirectory apply here as well.
  • Don’t create subdirectories inside subdirectories.
  • The module does not actually use file- or directory names, but the order of files and directories in the SD card’s FAT. The previously mentioned naming conventions just try to give you control over that order. If you properly organize your audio files in a folder on your PC first, and then copy that entire structure to a freshly formatted SD card, then (in theory) the FAT order should match the file- and directory numbering. See also this tool

Apart from adding some extra functions to the above mentioned library, I still have to find out how to reliably receive and read the module’s response to commands and queries. Once that I’m in full control over this (for its price) remarkably versatile module, it will be useful for a couple of ‘talking’ projects that I have in mind. Probably just mono and not ESP32 driven, but anyway: lots of fun for just €4.

 


Here’s an example of a function that I added to the DFPlayerMini_Fast library. Calling play_in_dir(2, 5) will play file 005.mp3 in subdirectory 02.

Using an existing function as a template, I first added my new function to the DFPlayerMini_Fast.cpp file:

Then I added these two accompanying lines to the DFPlayerMini_Fast.h header file:

and (inside the code for the class):

 

Explanation: according to the datasheet, the 10 (hexadecimal) bytes that have to be sent (by the sendData() function) to play track 005.mp3  in directory 02 are:

7E FF 06 0F 00 02 05 xx xx EF

7E FF 06 are the first three bytes for every command. The following 0F is de code for ‘play a file in a direcory’, the following 00 means ‘no confirmation feedback needed’ and is followed by the two parameters for the desired directory- and file number: 02 and 05. Then comes xx xx which are two checksum bytes that will be calculated and replaced by the findChecksum() function of the library before the command is sent by sendData(). Finally, every command has to be closed by EF.

As you can see, my newly added function play_in_dir() takes two parameters only, corresponding with two bytes in the command sequence. All other bytes of the sequence are either fixed or calculated by the findChecksum() function.

 

 

Dual Core Business

Almost a year after finishing my ESP8266-based Internet Radio project, I still have had no idea why running that sketch on the much more powerful ESP32 produces a heavily stuttering sound, resembling Michael Palin in “A Fish Called Wanda”.  So I wondered if I could solve this problem by dividing the audio streaming process between the ESP32’s two cores: filling the audio buffer on one core, and feeding the VS1053 decoder from that buffer on the other core.

Thanks to its builtin FreeRTOS, pinning a task to a specific core of the ESP32 is easy:

1. Put the code for the task in a function, wrapped in an endless for (;;) { } loop.

2. Create a task handle for the task in the main section of the sketch:

3. Create the task, specifying which function it should execute on which core:

 

(Since there was nothing in my loop() function (yet), I put a delay(1000); command in it. The loop() function runs on core 1, and I didn’t want it to claim too much of that core’s processor time.)

So my Radio sketch will have two independent tasks running on different cores: one that listens to the radio station’s audio stream for filling the circular buffer, and one for feeding the VS1053 decoder with bytes from that buffer. Both processes maintain their own pointer for accessing the circular buffer and the code guarantees that they will never catch up with the other process’ pointer. So there is no need to use a semaphore.

However, my first step into the dual core business produced an error. A ‘watchdog timer’ had become nervous and halted the ESP32. It turned out that including the following line in both functions solved at least that problem (bug?):

 

Now the ESP32 no longer crashed, and it actually produced sound. But alas, it was Michael Palin again…!

I can’t think of any reason for this behaviour. The serial monitor shows that the ESP32 has no problem filling the 30 Kbyte circular buffer, so feeding the VS1053 seems to be the bottleneck. I swapped cores for both tasks, but that didn’t help.

UPDATE: Meanwhile, I had become so desperate about finding a solution that I renamed the project “dESPeRadio“. But then, during a sleepless night, I wondered if the ESP32 might perhaps need a different policy for filling the circular buffer. The chunks it reads from the WiFi client per loop cycle have an upper size limit of 1024 bytes, in order to prevent the VS1053 from running dry during the process. But I didn’t specify a lower size limit.

For the ESP8266 this was OK, but the EP32 is so fast that, after it has rapidly filled the circular buffer, there will never be more than just a couple of free positions in the next loop cycle. All following cycles will therefore be very inefficient, causing so much overhead that the VS1053 actually runs dry in a very regular time pattern – the stuttering.

So I added just one single line for skipping the fill cycle if there’s less than 512 bytes of free buffer space available and uploaded the sketch without much hope, because so many ‘good’ ideas had failed before. What followed was complete silence…, which then turned out to be the pause between two movements of  Brahms’ fourth symphony! Then the music started in clean and, above all, uninterrupted sound!

The radio has been running non-stop now for a couple of hours. It looks like I finally have a solid and stable foundation for further development of my dESPeRadio on ESP32. Although using two cores isn’t necessary, it will be nice way to explore FreeRTOS.

In order to keep the code clean and readable, all future features and controls will be implemented as (optional) includible header files. I’ll make the full code available in my first github repository soon, so if you’re interested, stay tuned.

Plans for additional control options, apart from the already implemented keypad, are:

  • ☑ an embedded web server
  • ☐ the touch screen overlay of my display
  • ☐ IR remote
  • ☐ rotary encoders for volume, bass and treble

Other plans:

  • ☑ add VU meters (I love the analogue ones!)
  • ☐ use a FreeRTOS queue instead of a circular buffer

 

 

 

 

 

VS1053 + Matrix keypad

 

The VS1053b is a popular chip, used in many MCU controlled audio projects. It surprises me that nobody seems to use its eight GPIO pins, controllable over SPI by the master MCU. These GPIO pins enabled me to control my esp8266 based Internet radio by means of a matrix keypad, despite the fact that the esp8266 had only one free pin left.

The cheap 12-key membrane keypad shown in the picure uses 7 of these pins, wheras a 16-key matrix keypad uses all of them.

 

Adafruit’s VS1053 library has basic functions for adressing the chip’s GPIO pins. I wrote a simple demo sketch that prints the pressed key. In a real project, this information would typically be used to control other functions of the VS1053, like changing audio source or adjusting volume.

 

Make sure that pin definitions in the demo sketch match your MCU and connect RST to your board’s RST.

The VS1053_GPIO wiring for the demo sketch is different from what the picture shows. This is what I used for my 12-key membrane matrix keypad:

 

 

 

 

 

The connector’s column/row mapping for these particular keypads is usually as follows (from left to right in the picture): row0 – row1 – row2 -row3 – col0 – col1 – col2. The sketch can easily be adapted for a 16-key matrix keypad.

The basic idea of the sketch is:

  • keypadListen() sets all four row pins to INPUT with value 0; it sets all three column pins to OUTPUT with value 1. In this idle state, GPIO_digitalRead() will return B00000111 (decimal 7).
  • In loop(), we check GPIO_digitalRead(). A value higher than 7 means that one of the row pins has changed from 0 to 1 because it got connected to one of the column pins. This happens if a key on that row was pressed (that’s how matrix keypads are wired). We call keypadRead() to find the responsible key.
  • keypadRead() searches the row that caused the GPIO_digitalRead() > 7 condition, by checking each row pin until it reads 1. This gives us the row of the pressed key: R.
  • Next, it finds the column pin that is connected to row pin R by setting all column pins to INPUT with value 0 and row pin R only to OUTPUT with value 1. Checking each column pin until it reads 1 gives the column of the pressed key: C.
  • Now, keymap[R][C] holds the value of the pressed key (10 and 11 for * and #).
  • A simple debouncer is added for, well, debouncing.

 

On-air

All ESP-based Internet Radio projects that I’ve seen so far lack a clean modular setup that programs should have if they offer many optional features and support a broad variety of hardware setups. So I decided to write my own sketch from scratch, consisting of a main sketch for all hardware-independent basic functions, and a growing set of includible modules, one for each optional feature or hardware device.

Now I can compile tailor made sketches for the desired functionality (like support for Air Traffic Control stations) and actually used hardware only. I’m satisfied with the first results, and I learned a lot during this project.

Keypad controlled prototype in radio mode (left) and ATC mode (right)

The radio on the pictures runs without problems on an ESP8266 board, driving an Adafruit VS1053 breakout and a 2.4″ ili9341 TFT display. It can be controlled with a 16-key matrix keypad and via a browser.

The prototype uses almost 500 lines of code for the radio. David Bird’s excellent METAR functions take yet another 500 lines. Too big to post on this page, I’m afraid…

It has the following features (more will be added later):

  • Can play mp3 encoded streams from internet radio stations (up to 320 Kbps).
  • Built in webserver accepts control commands via a browser.
  • Can also be controlled with a 4×4 matrix keypad, using VS1053’s own GPIO pins.
  • Offers storage of 40 station presets (grouped in four  bands).
  • Displays station name and song title on TFT display and web page.
  • Displays a decoded weather report (METAR) when playing an air traffic control (ATC) channel. The first 10 stations in the preset list are reserved for ATC stations (I used David Bird’s excellent functions for METAR decoding).
  • A 30 Kbyte ring buffer is used for smooth playback.
  • Can play stations with redirected urls.
  • Designed to handle chunked transfer encoded streams if necessary (I was unable to test this, because I couldn’t find any station that uses this encoding).
  • Stereo dB levels can be read directly from the chip and be used to drive VU meters from PWM pins (or DACs on ESP32).

Below is a flow chart of the initial connection, followed by the streaming process (basically a finite state machine). In practice, it all comes down to determining each incoming byte’s function within the stream by keeping track of some counters and states. Once the actual streaming has started, an incoming byte can be one of the next types:

  1. audio byte – will be sent to a ringbuffer that feeds the VS1053 decoder
  2. metadata byte – a human readable character from the song title
  3. metadata size – an integer; multiplied by 16, it is the following song title’s length
  4. CR or LF – used as separators
  5. chunk size (if station uses chunked transfer encoding) – size of the next chunk

Déjà VU

 

My nostalgic love for analog VU meters made me buy two of these little Chinese guys. They contain good old-fashioned incandescent bulbs and came in a kit with an analog control board. Build quality looks OK and everything works as expected, although the control board seems to be designed to power LEDs instead of incandescent bulbs.

 

While preparing to connect them to my VS1053 audio decoder breakout, I discovered that additional functionality can be added to the VS1053b chip by means of plugins. One of these extra plugin functions allows you to digitally read VU meter levels! This means that, instead of using the analog control board from the kit, I can let an Arduino (or other MCU) translate digital VU levels to PWM values for driving the meters directly from GPIO pins (these simple VU meters are basically just volt meters). This provides total programmatic control over the meters’ sensitivity, needle speed etc.

Plugin files for the VS1053b chip can be found on the VLSI Solution site. Documentation can be a bit confusing for beginners like me. It took some research before I understood the mechanism, but then it was easy to upload a plugin, enable the VU meter and actually read its values. Here’s some info that might help (when using Arduino or ESP boards):

  • Plugins are loaded into volatile (temporary) memory of the chip. This means that they won’t harm the chip in case you make mistakes. Just restart your VS1053.
  • Because of the above, plugins have to be loaded within sketches that use them.
  • From the VLSI site, get the ‘.plg’ file of the plugin that you want to use. This file contains a plugin array (RLE encoded) and a LoadUserCode function.
  • Put the plugin array from the .plg file on top of your sketch (or in a header file that you include on top of your sketch). You may add PROGMEM to the array’s declaration.
  • If not using Adafruit’s library: put the example LoadUserCode function from the .plg file inside your sketch. Note that this function contains two WriteVS10xxRegister calls. You’ll need to write your own WriteVS10xxRegister function for writing a 16-bit value to a VS1053 memory address.
  • If not using Adafruit’s library: After the line <your_player>.begin() in your sketch, add the line LoadUserCode();
  • In case you use the Adafruit VS1053 library, you don’t need to include and call a LoadUserCode function. Instead, add the following line after <your_player>.begin(): musicPlayer.applyPatch(plugin, PLUGIN_SIZE);
  • After writing the plugin to the VS1053’s memory, enable the VU meter by setting the 7th bit (from the left) of the VS1053’s status register (address 0x01) to 1.
  • After enabling the VU meter, you can read dB levels from the AICTRL3 register (0x0F). The high 8 bits represent the left channel, the low 8 bits the right channel.

After I posted an issue with the VU meter plugin on their forum, the VLSI support team were kind enough to release a new version that fixes it. Now I can read decibel levels directly from the VS1053b chip and control my stereo VU meters with two GPIO pins.

Update: the plugin turned out to provide ‘leaking peak’ levels instead of real time readings, forcing me to do some programmatical reconstruction work.

Here’s a video of The Swinging Needles.

 

 

 

 

 

Internet Radio remote control

Several attempts to connect a display directly to the Esp32 Internet Radio made the radio unstable, so I decided to use a separate Esp8266 board for driving a 2.4″ TFT touch screen.

The result is a stand-alone unit that communicates with the radio’s Esp32 over WiFi, so it’s basically a removable display/remote control, with touch screen for sending commands and displaying radio station, song title and local time. It can be used instead of, or together with, the radio’s web interface.

Features:

  • Touch buttons for presets, volume, on/off and mute/unmute (toggling button texts)
  • Station name and song title centered on separate lines (updated every second)
  • Proper split of long lines
  • Correct handling of utf-8 characters
  • Displays local time

Note: Esp32-Radio is a project by Ed Smallenburg, available on Github.

 

Keep streaming

While still in an early stage of research for my Internet Radio (previous post), I came across Ed Smallenburg’s comprehensive Esp-radio project on github. It’s a very well documented project with a heavily commented sketch.

Instead of proceeding with my own (basic) Internet radio project, I decided to take Ed’s nearly flawless Esp-radio as a starting point, and adapt it according to my personal needs. Most important change will be the addition of two small oled displays for emulating analog VU meters.

There’s also an (even better?) version for ESP32 by the same author. This persuaded me to finally enter the ESP32 world, so now I’m the proud owner of a Wemos LOLIN32 Pro. My hope is that this powerful dual core board will be able to drive the entire setup of radio, TFT touch display, IR remote and VU meter oleds.

Update:

Unfortunately, my Wemos LOLIN32 Pro is from a batch with a faulty serial chip (CP2104). Most of the time, my PC will not recognize the board when I connect it, resulting in the serial chip becoming very hot within 5 seconds. This is my fourth Wemos device, two of which were crap!

However, when the board is recognized, it works fine and I even managed to upload and run the ESP32-Radio sketch. As for performance, I would say that both versions (ESP8266 and ESP32) seem equally satisfying and stable.

Below is a screenshot of the web interface. I removed and changed some buttons from the original version, and added a “now playing” display that updates every two seconds.

My futher plans with an ESP32 based Internet radio will have to wait for the replacement of my board. Planned changes are mainly peripheral and cosmetic, like adding VU-meters and a frequency analyzer. To be continued (stay tuned…)

Stream it!

 

The purchase of this versatile breakout board from Adafruit immediately made me put on hold all my ongoing projects. May the stream be with me!

 

This breakout board, built around the VS1053B chip, decodes various digital (stereo) audio formats such as MP3, AAC, Ogg Vorbis, WMA, MIDI, FLAC, WAV (PCM and ADPCM), and sends the analog signal to a female headphone plug (included as a breadboard friendly separate part). It can also record audio in both PCM (WAV) and compressed Ogg Vorbis format. All functions can be controlled over SPI.

Other features of the breakout board are:

  • microSD card holder for storing audio files
  • works with 3.3V and 5V boards (5V compliant pins)
  • 8 digital GPIO pins (not 5V compliant)
  • Volume, bass and treble control
  • a microphone input port
  • MIDI mode (reads MIDI data on the UART pin)
  • Additional functions (e.g. spectrum analyzer and VU meters) available via plugins

I had noticed the board before, but what made me decide to purchase it was this very simple esp8266 Internet Radio sketch on the Adafruit site. The fact that the VS1053 chip can handle mp3 live streams, delivered by an esp8266, means that it can be used for building a music streamer/internet radio! Since the esp8266 is capable of acting as a web client and a web server at the same time, it should be possible (in theory) to control the player with any web device (apart from several additional options, like buttons, IR remotes, rotary encoders, keypads, joysticks…).

After soldering the included header pins, I started with some simple MP3 playback from an SD card. My vintage Sennheiser Ovation was impressed! Then, with great expectations, I ran the Internet Radio sketch…. No sound! Not even debug messages on the serial monitor. Although Adafruit only, my hardware wasn’t from their new Feather/Wing line, for which the sketch was written. After quite some desperate attempts, almost losing hope, out of the blue came Bob Dylan’s “there must be some way out of here“. How appropriate (just as “something is happening, but you don’t know what it is” would have been). What did the trick was connecting the RST pins of the VS1053 and esp8266 boards, although the Adafruit example sketch suggests otherwise in the following line:

 

Here’s what I plan to do next:

  • Add a display (probably SPI, claiming two additional esp8266 pins; only one left…)
  • Make the esp8266 run a web server, listening to commands via a web page, very similar to the one I wrote for controlling my Transporter
  • Add manual control (buttons, IR, keypad?). The board’s own GPIO pins can be used for simple high/low devices like buttons
  • Support a list of selectable presets, pointing to internet radio stations
  • Make it possible to switch between radio mode and file mode (play ‘local’ music files)
  • Implement a larger (cyclic) buffer for more stability