Rocket Tracker Part 12.2: Dual Deploy Firmware Support
With my partially operational dual deploy board (mostly functional aside now with a bodged 5V power supply), I was able to get a pretty good start on firmware support for a bunch of the different devices on this board (addressable LEDs, the IO expander for pyro channels, the analog to digital converter and the PWM generation chip).
LEDs:
The first thing I decided I’d work on support for was the addressable LEDs because ESP-IDF has support for the type of addressable LED I’m using, so that was pretty convenient. After a little bit of messing with the driver configuration, I managed to get the DMA based driver working. (It took a bit more work than I expected – I had to move some of the tracker’s processing over to the second core because there weren’t enough free interrupts for the RMT-based driver on the first core)
Pyros:
The IO expander used for firing and checking continuity for the pyro channels is a pretty simple IC. It only has three registers accessible over I2C used to set the direction of the pins, read, and write. The IO expander itself was pretty straightforward to get working and integrate with the rest of the firmware. I did a little bit of testing and found that my continuity checking circuit using the load driver ICs seemed to work! I haven’t fired any actual E-matches yet – I’m not planning on doing that until I get more advanced support in the firmware for actual dual deploy control logic.
Analog Inputs:
The analog to digital converter on the board is as it’s referred to by the manufacturer, a “cost optimized” analog to digital converter. Although it has 4 channels, it only has one actual ADC - the four channels are accessible through a multiplexer that you have to configure over the I2C bus. On the bright side, it does have 12-bit resolution. To read four channels “at the same time”, I implemented a sort of round-robin reading procedure that reads the analog to digital converter at 4x the requested (single-channel) sampling rate set in the configuration of the tracker. Using a software timer set at this rate, in each of those four intervals the firmware reads the current ADC output (the value of the currently selected channel) and then switches the multiplexer to the next channel (which starts a measurement on that channel).
Detour: Logging
Now that I had got the analog digital converter working, I wanted to get a little bit into more software support for the dual deploy board. So I got to work making facilities for logging analog digital converter measurements into the board’s log memory. Because of the logging system upgrades I made a while back, it was pretty simple. I just added another type of data to log and assigned a new ID to it. I did get a little sidetracked though, because while I was messing with the dual deploy board, I had an idea: If I have this big chunk of log memory, why not write the debug log messages (typically output on debug a serial port) to it as well? Since my logging framework supports variable length messages, adding that functionality was no problem at all - ESP-IDF provides a convenient hook designed for custom log output. With that feature added, now when I flight test the tracker, after parsing the logs, I can view the debug output in case anything went wrong. It’s actually even better than viewing the debug output over serial, because the log messages are stored with precise timestamps alongside other data that is logged, which allows for easily showing the sequence of events before and after a certain bug or failure.
(Attempted) PWM Output:
With the logging component of the dual-deploy firmware mostly done and the analog digital converter working, I got to work on the PWM output. When I started, I didn’t know really what I was getting myself into. Trying to get proper PWM output working turned out to be a multi-day endeavor ending, disappointingly, without success.
Before designing the dual deploy board I looked at a bunch of different “servo expansion boards” with the intention of figuring out what they use to produce the servo PWM signals. Usually these boards would provide either 8 or 16 PWM channel outputs. To produce the 1 to 2 millisecond servo control pulses, they all used LED controller integrated circuits – certainly not what they are designed to do, but these ICs could generate PWM pulses at different frequencies with duty cycles (16 bit controllable) that allowed for fairly precise output within the 1 to 2 millisecond range of servo pulses. When I designed my board I found a 4 channel LED driver integrated circuit – also designed for PWM output, but I overlooked some important limitations. When I looked through the data sheet, I looked over the notable features and description, but I didn’t really dive deep into it – and I made some assumptions that were misguided. And that’s probably my biggest regret for this board, because the LED driver integrated circuit that I chose isn’t really capable of producing clean PWM pulses within that 1ms to 2ms range.
First of all, I didn’t realize when I decided on this integrated circuit that the PWM output had a fixed frequency – that alone makes producing the 1-to-2ms servo pulses nearly impossible. Since there’s no way to control the frequency of each of the steps in the PWM generation timer, there’s no real way I could produce pulses at a specific frequency (50Hz). This integrated circuit is solely designed to generate a 0-to-100% duty cycle PWM signal at 97kHz, which seems pretty arbitrary. I guess it’s perfectly fine for dimming LEDs – but certainly not helpful for generating RC hobby servo signals. I thought I might have a saving grace, though, because this integrated circuit also has a nice blink feature as well as global brightness control. The global brightness control operates at 190 Hz, which is actually really, really, coincidentially nice because 4 channels at 50 Hz adds up to approximately 190Hz (well… I know it’s 200Hz, but hobby servos are pretty tolerant of imperfect signals for the most part) I thought, hey, why don’t I do the same thing as I did on the analog to digital converter and just do round-robin time-division-multiplexing of the channels, using the global brightness/dimming control to adjust the duty cycle of these 190Hz pulses, and switching channels at 190Hz (one pulse per output channel). Because the global brightness control basically acts as a mask over the individual brightness control (which is that fixed 97 kilohertz PWM signal), I thought that I would be able to set the individual channel duty-cycles at 0% or 100% to select which channels are enabled. Unfortunately, though, this integrated circuit has a super annoying and really oddly specific quirk. The PWM settings are 8-bit (0 to 255), 0 of course is no duty cycle (completely off), but annoyingly, 255 is not 100% duty cycle! According to the datasheet 255 is around 97% duty cycle… argh! This means that even with the global brightness dimming control being able to produce pulses between 1 and 2 milliseconds, those pulses wouldn’t be solid pulses. They would be pulses divided up by a bunch of super tiny little spikes caused by the individual 97kHz brighness control…
At this point, I was pretty discouraged, but I have trouble giving up on something like this until I have tried absolutely every possible solution. I still thought that maybe I could make it happen by manually turning the level of the pins on and off and completely ignoring the PWM generation on the chip entirely and just using it as an I.O. expander. Yeah, I know that’s a dumb idea for so many reasons – but it might be the only workable option. Implementing this turned out to be my most time consuming endeavor because although I could easily switch the pins on and off over I2C, switching the channels on and off with timing accurate enough to generate stable 1-2ms PWM pulses without significant jitter turned out to be a nightmare. I won’t go into the specifics on this, because it’s not super interesting, but basically the way the ESP32 handles software timers - and more specifically - the way that the RTOS schedules I2C transactions. makes it incredibly difficult to precisely control when an I2C transaction actually begins. I managed to get PWM pulses generated at the correct rate (50Hz) and within 1 to 2 milliseconds, but depending on what other timers and tasks were running in the firmware, the width of the pulses would vary significantly by around a quarter of a millisecond! Although 0.25ms is really small, it unfortunately makes the output completely unusable for servos because they’d be jittering all over the place (0.25ms = 45deg).
At this point, I pretty much admitted defeat. Although I made some bad decisions in choosing this integrated circuit, I tried my best to get it working, but it just was slightly out of reach. This combined with the power supply issues from the 5 volt buck converter have led me to decide that it’s probably a good idea to just redesign this board. And now after having some experience with these different components, I think I have a pretty good idea of what I can do to make this board much better in the next revision.
Thanks for reading! In the next post I’ll go over my ideas for the next version of this dual deploy board, aiming to fix some of the issues and limitations I’ve encountered while working on the firmware.