April 16, 2023

It’s been a fairly unfocused week, but I’ve somehow managed to allocate an hour here or there to letterbox. My goal has been to validate my current design choices: the use of shift registers to drive the display, fed by an ESP32-S2. Amazingly, I think I’ve actually managed to answer all the questions I had about that.

Of course, it all had to start somewhere, and that somewhere was with installing the ESP32 development environment, which I have never so much as touched before.

Why Not Arduino?

Using an ESP32 Feather would be trivial with Arduino. Unfortunately, as near as I’m able to tell at least, there is no way to work with the ULP from the Arduino environment. That rules it out; the ULP is absolutely required due to the power-hungry nature of the main cores.

Since this is just for experimentation, the esp-idf makes sense for rapid development. Or so I hope, at least. I have vague ideas of a future board package for the Arduino environment that would allow one to use my little display as an Arduino target, with the display firmware handled in the background, but that day is not today.

Installing the Pre-Requisites

The ESP-IDF documentation is quite a lot better than I would have expected. The getting started docs in particular give good instructions, with only a few tweaks necessary to get everything working correctly, probably because I use Rocky 8.x instead of a newer distribution.

The biggest annoyance for me was having to package dfu-util, which wasn’t available in any of my many repositories, including epel. Instead, I stole Fedora’s source RPM for it and rebuilt it for el8. If you want it, you can download it here, but caveat emptor; I haven’t exactly tested the software directly, and I don’t know if esp-idf uses it for my use case.

The remaining steps can be summed up thus:

$ sudo yum module switch-to python38
$ sudo yum install python38
$ sudo update-alternatives --set python3 /usr/bin/python3.8
$ sudo sudo yum install --enablerepo=epel --enablerepo=powertools git wget flex bison gperf python3 cmake ninja-build ccache dfu-util libusbx
$ git clone --recursive --depth=1 https://github.com/espressif/esp-idf.git
$ cd esp-idf
$ ./install.sh all

Note that my repos (especially epel) may be named differently than yours; don’t forget to adjust accordingly.

From there it was just adding the export.sh call into my bash profile; you can do the same for whatever shell you use. The name of the script is given in the installer output.

And then you’re ready to go.

The Experiment

My goal with the initial experiment was simple: I needed to verify that the ULP is fast enough to handle what we need of it. Basically, we want the ability to scan the whole display sixty times per second at a minimum. If it can’t do that, then we’ll need an alternative design.

I started with a copy of one of the hello world type examples (I forget which one) and simply replaced the C code with my own. There are actually two separate programs: the main program that runs on the main ESP32 core, and the ULP program that runs on the ULP core.

Thankfully, there is a C-compatible RISC-V ULP core in the ESP32-S2. This means that the ULP program is just another C program.

It took a bit to figure things out – while the esp-idf docs are suprisingly good, they are lacking in a few areas, and the ULP is one of them. For example, they don’t have a good description of how to access the GPIOs from the ULP, and you (at least apparently) can’t do it with the standard GPIO functions. Luckily there’s a Dallas One-Wire ULP example that shows the generalities.

My program just takes a pre-initialized memory area containing enough bits for all seven rows (48 bits each) and shifts them out of a data pin. Each time it does, it pulses a data clock line, and at the end of each row it pulses the row clock line. Then it goes back to the beginning and starts over.

This is likely very similar to the basic implementation we’ll use in the real thing, which makes it a very relevant test.

Then I hooked up an oscilloscope to the pins in question, flashed the firmware, and had a look at the results.

The Results

In short, the test was successful. There are no deliberate delays anywhere in the system, but it still comes in well under the maximum speed of the ‘595 shift register (and with sufficient period on the clock and latch pulses that I’m not at all worried). The 595’s should, in theory, work just fine with this output.

On the other side of the equation, the output is fast enough that it allows plenty of time. I was concerned that memory access might be too slow or any number of other issues, but that does not appear to be the case. As currently configured, I’d guess the ULP would spend in the neighborhood of 80% of its time sleeping if I left the timing the way it is (which would probably not work very well).

In production I’ll have to implement timing controls so that the rows stay lit for the right amount of time, but that should be relatively simple to accomplish. And if it’s dynamically settable, then we’ll have dimming built in.

My only remaining question is whether to support scrolling in the ULP code, and while it is likely, I’ve not yet decided. We’ll see.

Power Consumption

This particular experiment also gave me an opportunity to see the power consumption figures on the ESP32-S2 under near real-world conditions. Well, for the ULP at least; I didn’t bother tinkering with the WiFi in this round of testing. I was actually quite surprised by the results, though.

When the chip first comes up – and the main core is active – it averages in the neighborhood of 20mA (with the wireless disabled obviously). After ten seconds (give or take), however, my test code puts the main core into deep sleep, leaving the ULP to do all the work of constantly scanning the display.

The total draw is just under a quarter milliamp.

This is actually a little higher than the datasheet suggests, but not all that surprising. First off, there are other parts on the Feather that are likely taking a bit of juice (not least of which is a 3.3V linear regulator, which just cannot be that efficient!). Second, we’re actually running a program a lot of the time, and it’s a bit unclear to me what the conditions are for the numbers listed in the datasheet.

Either way, 250 microamps is perfectly acceptable for our current purposes.

A Potential Issue

The only item of note that concerned me is the ESP32’s disconcerting habit of occasionally failing to start the ULP when it should. It doesn’t happen all that often, but it does happen, and that’s a problem. I’m going to have to look at reliably reproducing it so I can figure out what’s causing it and/or how to work around it, but that will come in time.

The only other annoyance I have is the issue of having to hit the boot button while resetting the module in order to re-flash it. That’s the downside of using the integrated USB support I suppose. I’ll worry about extending that functionality later.

Next Steps

Now I need to put the first draft of the schematic together. It should be relatively straightforward, and I have parts of it done already. That effort is happening in KiCad.

I also have a bunch of ‘595’s on order.

That’s the major next task: I’ll actually breadboard it and write the first version of the firmware. Once that’s validated and has the features I want, I can order some PCBs.

I can’t wait!