Implementing a Bluetooth Speaker Using the ESP32

I have made several nixie clocks built around the ESP32. They incorporate a speaker, and I thought it would be nice if I could also stream music to them. The ESP32 has built-in Bluetooth support, so I figured I should try and use that first – essentially turn the clocks into a Bluetooth speaker.

I set about looking for examples and rapidly discovered that this was a little-used feature. Almost all the coding examples on the internet focus on Bluetooth BLE. I needed to use Bluetooth A2DP. Furthermore, I needed to implement an A2DP sink – that is something that audio data is sent to rather than from.

All of the code I could find was very low level and hard to understand – it would have been hard to incorporate it into an existing code base. It was all written in C, had zero encapsulation and seemed to be littered with code that was unnecessary for what I was trying to achieve.

To cut a long story short I wrapped it all in to some C++ classes. There are four main classes:

  • Audio
  • DataSink
  • EventSink
  • Out

Audio

This class just initializes the bluetooth protocol stack.

DataSink

This class listens for audio data and writes it to a ring buffer.

EventSink

This class listens for A2DP lifecycle events such as connected, suspended, configure etc. and publishes the events to any objects that subscribed to them.

Out

This class reads the audio data from the ring buffer and streams it out to an external DAC connected to the I2S ports.

The code is here. It can be used with or without the ESP32 Arduino framework. There is an example that uses the Arduino framework in the repo.

In the end I’m not using it with my clocks. All the code together blows the heap allocation and requires me to use a different memory layout. I might look in to sorting that out later.

Nixie Clock With Vacuum-tube Power Supply – Part 4

This is the fourth post about a nixie clock project that is powered by a vacuum tube power supply, rather than the more common 12V or 5V wall wart. The previous part can be found here. I’m going to take a break from talking about the power supply and veer off into talking about the controller hardware.

One of the features I had wanted to add to a nixie clock for some time was to simulate the ticking noise of a mechanical clock. To that end, I had previously bought an ESP32 Pico dev kit (because it support I2S), but then done nothing with it. It was time to resurrect that idea. By the way, the reason for choosing the Pico was that the chip itself requires the minimum of additional hardware to build my own PCB using the chip.

Now that I was seriously developing for the ESP32, I actually delved deeper into the software platform and discovered that it is running RTOS. This is a real time operating system with support for multiple prioritized tasks. In fact, the ESP32 arduino platform uses a single RTOS task to implement the loop() callback. My code for the ESP8266 basically split the work in the loop() method into multiple tasks, so I figured I could drop all the scaffolding I had written to do that, and just use one RTOS task on the ESP32 for each task that I wanted the ESP32 to complete (the clue is in the name!), rather than stuffing a bunch of calls into the loop() method.

The first thing I wanted to play with was playing sounds – that was the whole point of considering the ESP32 for the core processing. I started by looking around for libraries that I could use to play sounds and settled on ESP8266Audio which, despite the name, has good support for the ESP32. For it to be useful, I also needed to buy a SD Card reader module and an I2S amplifier module, both of which are available from Adafruit.

One advantage of the ESP8266Audio library is that it uses DMA to feed the data to the I2S pins of the ESP32. So theoretically, user code just has to keep the DMA buffer full, and then the data is pushed out of the pins via interrupts. It seemed like this would be the right thing to do as I was going to be loading up the processing to handle more and more tasks. I initially had trouble with the sound glitching. It was difficult to tell if this was because of the software or the breadboarding I was doing. As I added more tasks the glitching got worse. In the end it was easily fixed by increasing the DMA buffer count from 8 to 64!

The next task I wanted to add was driving neopixels. I had tried this before on the ESP32 with little success. It tended to be very glitchy. Looking around I discovered another library called NeoPixelBus which, like ESP8266Audio, could use DMA to feed data to the NeoPixels. Fortunately there are two DMA channels on the ESP32 and also fortunately, I could tell the libraries which one I wanted them to use. However, the neopixels would still occasionally glitch. At this point I wasn’t sure if this was because of my breadboarding or the software, so I chose to ignore it. It turns out that there is a bug in the ESP32 DMA firmware. NeoPixelBus changed their default support to use the IR remote support, but I still found that it glitched occasionally. Finally, when I upgraded the Arduino platform to v1.0.4, the glitching stopped.

Next I needed to drive the Nixie tubes. I implement fading and cross fading all in software, so I need better timer resolution than can be provided by the task manager. So I implemented this in an interrupt routine. I had already done this for the ESP8266, so I was familiar with the need to decorate all interrupt methods so that they would reside in IRAM. However it turns out that that wasn’t enough.

Firstly, my interrupt code is in C++ and uses virtual functions. It turns out that for the ESP32, the compiler is putting the v-tables in flash, so I had to resort to copy/paste and get rid of all of the virtual functions. BTW, the version of the ESP8266 platform I was using was storing the v-tables on the heap. Later versions allow you to specify where the v-tables are stored. It would be nice if the ESP32 platform did the same.

It still crashed, and I eventually discovered that g++ was generating function calls to do long division. That function is not in IRAM. The millis() function (which is in IRAM) uses long division. I guess no one had ever called it from an interrupt routine before? Thankfully the developers changed the default compiler flags to inline the code instead. So all was good.

Next I wanted to use a WiFi manager library to handle setting up the connection to a router. Thankfully, the one I prefer to use – ASyncWiFiManager was also the only one that had been ported to the ESP32. However, my iPhone simply would not connect to the access point – my laptop had no problem. Turns out that there was a bug in the ESP32 core which was fixed just after I discovered the issue. An aside: this is interesting, I am pretty sure the ESP8266 core has a similar problem, as connecting to the AP from an iPhone is patchy on the ESP8266.

So then I was implementing my Web GUI, which uses the SPIFFS library to store the assets. That’s when I discovered that the SPIFFs library on ESP32 was broken. So I fixed that and the fix was incorporated into the Arduino core.

So finally, everything that I needed (software wise) worked great and has, in fact, been extremely stable.

Nixie Clock With Vacuum-tube Power Supply – Part 3

So I had a functioning prototype (see the end of Part 2), but at about this time I bought an old oscilloscope:

Old Oscilloscope

So I started looking around for oscilloscope clock kits and eventually I hit this site (which is an overall fascinating site). That site had a page that introduced me to voltage regulator (VR) tubes. He was using two tubes called 150C2 (aka 0A2) to provide +150V and +300V.  Now the great thing about VR tubes (for me) is that they are also cold-cathode tubes – i.e. they contain some mixture of noble gasses, and they glow. It looked like I could easily produce a regulated power supply using these tubes, but of course there was a catch – or several in fact:

  1. These are like nixies or any other neon tube – they need to be current-limited. The 0A2 will take a maximum current of 30mA. The way that Grahame was using them was to tap off the required voltage after the current-limiting resistor. So that resistor had to be sized to allow the minimum required current for the VR tube (5mA in the case of the 0A2) + the maximum current for the nixie tubes. I was planning on drawing around 50mA, so I would need a resistor that limited the current to 55mA. However when all the nixie tubes are off, that would mean the 55mA would be flowing through the VR tube, which exceeds its maximum by 25mA. I suppose I could have used several in parallel, perhaps even one VR tube for each nixie tube! However there is the second issue:
  2. The regulated voltage that they establish is, at the most, 150V (different VR models have different maintenance voltages). Again, I could use two in series, but now we are getting a little ridiculous.

It was time to do some more research. The solution is to use the VR tube to produce a reference voltage and then construct a feedback loop that measures the difference between this voltage and the desired output voltage and adjusts the output to stay at the desired level. At a high level, it looks like this:

Regulator block diagram

Together, the system tries to keep Vc equal to Vr, and therefore Vout constant, no matter what happens to Vin. Though there are limits to how much variation it can handle. In this system

Vc=VoutR2R1+R2

And since Vc = Vr

Vout=VrR1+R2R2

We have the reference voltage – it is a VR tube. What about the other three? The comparator and error amplifier are one device – in this case a triode. The controller (usually called the series pass tube, or just the pass tube, in this toplogy) could also be a triode. This diagram, from a 1947 issue of Radio magazine, illustrates the components nicely, leaving out various supporting resistors and capacitors:

High-level regulated power supply design

However, I will use a pentode for the pass tube, because I’m going to use a single tube that contains both a triode and a pentode – the ECL85 (aka 6GV8). The VR tube will be a 0C3, because it is a 108V VR tube and it looks nice!

As it turns out, the ECL85 also has a 6V3 heater, but it needs it to be at a potential that is within 100V of either cathode. That means I can’t use the same 6V3 transformer winding for both the ECL85 and the rectifier tube. In addition, there is significant voltage drop across the pentode and the whole thing will need more current to drive it, so it was time to look for both a new transformer and a new rectifier tube. I ended up going with the 5W4G, which still has a reasonable forward voltage drop and can handle the additional voltage and current. In addition it has a 5V heater which works well in this setup, because transformers that have one 5V and one 6V3 winding are more common than transformers that have two 6V3 windings. For the transformer, I settled on another Hammond, the 270EX.

The 5W4G has a directly heated cathode, unlike the 6CA4, which means that it is at the same potential as the cathode. So now I have a problem. Both the 5V and the 6V3 windings are at different potentials than the main secondary winding, which means I can’t use either of them for my 5V supply, at least directly, as my 5V supply needs to share a 0V rail with the main secondary. I’ll come back to this later – I want to talk about the control circuitry first. I’ll finish this post with the final circuit diagram of the power supply:

Final Regulated Tube Power Supply

Nixie Clock With Vacuum-tube Power Supply – Part 2

In Part 1 I covered the genesis of this project and my first stab at a vacuum tube power supply.

One of the things I wanted to do was provide all of the power for the clock from the transformer. My regular clock circuit only needs 5V, so I figured I could get this from the 6V3 filament winding using a bridge rectifier and a 5V voltage regulator. This is only really possible because the 6CA4 rectifier tube uses an indirectly heated cathode. That is to say, the heater is not connected to the output pins, so it can be at any potential with respect to the output voltage. In particular, it means we can tie one side of the heater to the common ‘ground’ or 0V rail. The HV ‘ground’ is the center tap of the secondary:

Circuit, showing heater

Some of you may have figured that that is cutting it a bit too fine. A bridge rectifier using regular diodes could drop around 1.4V to 2V depending on the current draw. The voltage regulator would drop another 2V, so it needs a steady 7V in. So if the smoothing were perfectly efficient, we would go from 8.9V (6.3*1.414) to possibly less than 7V. Simulation with LTSpice shows this is optimistic.

Another alternative would be to use Schottky diodes for the bridge, and simulation show this would probably work. However, I decided to use a simple voltage multiplier on the 6V3 AC which gives me better smoothing for fewer components. So the whole circuit now looks like this:

Circuit with 5V section

You’ll note that this diagram is slightly different. I have cut the direct connection between pin 4 of T1 and the 0V rail of the HV section. Instead I have explicitly shown each section (the voltage doubler, HV and 5V sections) with a separate connection to ground. This is because, as it is, I am only using one half of the voltage doubler. This is just one diode drop, rather than two (as with the a full-wave bridge), so the voltage is enough for the 7805 regulator. If I wanted to use the full doubled voltage, I could just move the voltage-doubler ground to the point labelled A instead.

This is all very well, but it turns out to have a couple of draw-backs, as will become apparent later.

Current Limiter

The HV power supply is unregulated – which is to say, as you draw more current, the voltage starts to sag. This is because the forward voltage drop of a rectifier tube increases as the current increases. To get around this I decided I would use a small constant-current circuit to drive my Nixie tubes, in place of the usual current-limiting resistor. This basically works by connecting the source of a PMOS transistor to HV via a resistor (Rset) and then biasing the gate a fixed voltage below HV (Vgs) like so:

This is the equation for the resistor, given the desired current (Id):

Rset=VgsVgs(on)Id

Where Vgs(on) is the voltage drop between the source and the gate, which you can get from the transfer characteristics chart of the data sheet. A rough estimate would the typical gate threshold voltage.

Of course, the trick is to keep Vgs constant, even though HV is varying. To this end, I used a small isolated DC-DC converter to boost 5V to 12V and then attach it to the circuit as shown above. The gate takes virtually no current, so the boost converter can be very small. Using a voltage divider let’s us get to 10V and using a small potentiometer in one leg of the voltage divider allows us to adjust the voltage to get precisely the current we want.

Following some advice I received on the Neonixie google group, the actual circuit is a little more complicated, to provide some protection to the MOSFET. I designed some boards to act as sockets for the nixie tubes that included all this circuitry:

At this point I had decided I would ultimately want to use all of this to build a clock that would feature the large NL7037 tubes I had been collecting. I had been meaning to build a clock with an industrial feel to it to match the look of these tubes, and I felt that this would be the one. Finally here is a shot of a test I made using one of the tubes and adapters:

Divergence Meter Battery Mod

The divergence meter I built can be powered by an internal 9V battery. However, a regular 9V battery doesn’t last very long (between 10 and 45 minutes). LiPo 9V batteries last much longer – over 3 hours, though they aren’t really 9V as they are actually two regular LiPo batteries in series – so the actual voltage is at most 8V, dropping to 6V just before the power runs out.

However, that still means that you have to open the clock up to change the battery when it is out of power. A better alternative would be to include a power-sharing charger inside the case, so that the LiPo will charge up when the clock is plugged back into the power. A quick(ish) search of the internet turned up this 9V LiPo charger module, which is small enough to fit inside the case along with the battery. So that gave me the charger, but I needed to add the load-sharing part. A load-sharing charger will use some of the input power (input from the wall adapter) to charge up the battery, while the clock runs off the rest. It also needs to disconnect the battery from the clock as long as the clock is plugged in to the wall adapter, but instantly connect the battery to the clock as soon as it is unplugged from the wall adapter. This is actually pretty easy, and my circuit is basically the one described here.

Here is the basic circuit:

Load Sharing Charger

The load-sharing part is inside the dashed box. Here is what my implementation of this looks like. I used a surface-mount MOSFET, because I happened to have one already:

Load Sharer

This works as follows:

  1. As long as there is power on Vin, the P-channel MOSFET (Q1) will be turned off, preventing any power from being discharged from the battery and also preventing any power from Vin being directly applied to the positive terminal of the battery. Power from Vin will flow to V+ through the Schottky diode (D1).
  2. While there is power on Vin, the TP5100 module will also be able to charge the battery, if necessary.
  3. As soon as power is removed from Vin, the 100K resistor (R1) will pull Vin to ground, which will turn on the MOSFET allowing current to flow from the battery to V+.
  4. This is why the Schottky diode (D1) is needed. It stops the gate of the MOSFET being pulled high again by the battery, which would turn it off, etc. The only reason that D1 is a Schottky diode, is to minimize the voltage drop from Vin to V+.
  5. The LED (D2) shows when the battery is being charged.

I checked it all out before stuffing it into the case:

Load Sharing Charger

To integrate this with the clock, you have to cut the track from the Vin pin on the barrel connector (power in), then wire that to the Vin connector on the battery charger. Then wire V+ from the load-sharing board to the other side of the track that you cut – just find an easy place on the board to do that.

To re-use the existing switch, remove the optional diode (if you installed it) and just wire the switch up as shown in the circuit diagram above.

Finally, if you choose to use the charging indicator LED, you will have to drill a hole in the case somewhere to install it.

Here it is in the case:

Charger in the Case

View Showing LED Placement

I took a time-lapse video of the clock running off battery. As you can see, it lasts a little over three hours:

Display the Time on an Old Frequency Counter

A while back I bought an old frequency counter that has a Nixie tube display. It is a Japanese SF-87A made by the Sansei Electronics company. I bought it for the tubes – eight CD66 – however, when it arrived, it was in very good condition and worked just fine, so I decided not to harvest the tubes from it. I subsequently used it to display the frequency of my Nixie power supply, but that was just looking for an excuse, I didn’t actually need to use it for that:

Displaying the frequency of my Nixie power supply.

This left me in a bit of a quandry, but not too long ago Alic Loeliger suggested on the Nixie Clocks Fan Page on Facebook that I could get it to display the time by just sending the right number of pulses to it in a given time. This was such a simple idea that I had to try it.

The maximum gate time of the counter is 1 sec, and the largest number I would need to display would be 125959 (aka 12:59:59) for a 12-hour clock, or 235959 (aka 23:59:59) for a 24-hour clock. In other words, I would need to generate (at most) either a 126KHz signal, or a 236KHz signal. As I was doing this in software, because I am a software engineer, I went for the lower of the two numbers – I wanted to make sure that one of the processors I had available would be able to do it. I had a choice of an Arduino Uno, or an ESP8266. I ended up going with the ESP8266, which I programmed using the Arduino ESP8266 toolset.

At first, I split the number of pulses evenly over the gate time. i.e. if I needed to display 10:00:00, I would send send one pulse every 0.00001s. Then I realized that the counter just counted the number of pulses that occurred within the gate time, then divided that count by the gate time to get the frequency.  In other words, all I had to do was generate the right number of pulses in less than one second. However, when I tried this, it didn’t work – I assume the pulses were too close together for the counter to detect them. So, back to sending them spread over the gate period.

The result is kind of, sort of OK. The higher the frequency gets, the less able it is to accurately display the seconds – there is some ’rounding’ involved. Here is a video of it displaying 03:07:21 – note I have no control over the placement of the thousand separators:

Here is the source code:

#include "Arduino.h"
#include 

// Use pin 0
#define PIN 0

volatile unsigned long scaledPeriod = 0;
unsigned long oldMicros = 0;
unsigned long counter = 0;

/*
 * generate the pulse train in a timer interrupt. The function uses
 * the period, rather than the frequency. This is in 1/100,000,000s
 * units
 */
const int counterPeriod = ESP.getCpuFreqMHz() * 10;

void ICACHE_RAM_ATTR isr() {
    uint32_t ccount;
    __asm__ __volatile__("esync; rsr %0,ccount":"=a" (ccount));
	timer0_write(ccount + counterPeriod);

	unsigned long newMicros = micros();
	// micros is millionths of a second. Our period is in 
	// 100,000,000 of a second, so we multiply the difference
	// by 100
	unsigned long diff = (newMicros - oldMicros) * 100;
	if (scaledPeriod > 0 && (diff > scaledPeriod)) {
		digitalWrite(PIN, HIGH);
		oldMicros = micros();
	} else {
		digitalWrite(PIN, LOW);
	}
}

void setup()
{
	// Set to some arbitrary time
	setTime(3, 7, 20, 1, 2, 2018);
	pinMode(PIN, OUTPUT);
	timer0_isr_init();
	timer0_attachInterrupt(isr);

	timer0_write(ESP.getCycleCount() + counterPeriod);
}

unsigned long  oldTime = 0;

void loop()
{
	unsigned long newTime = hour() * 10000 + minute() * 100 + second();

	if (newTime != oldTime) {
		oldTime = newTime;
		if (newTime != 0) {
			// Need a numerator that is >> newTime could be
			scaledPeriod = 100000000/newTime;
		} else {
			scaledPeriod = 0;
		}
	}
}

This code has some problems! As newTime gets larger, scaledPeriod loses more and more precision. Furthermore, because the interrupt routine is called at a fixed periodicity, it is essentially sampling the waveform. Both of these things mean that the displayed time gets less accurate as the absolute size of the number being displayed is increased.

In retrospect, it would be better to adjust the periodicity of the interrupt routine to match that of the waveform we are trying to generate. That would be an improvement, but we would still have problems caused by the rather coarse granularity of the timer we have available.

Still, the result isn’t too bad.