Micro Sawppy RC Input Via ESP32 RMT
As one of many options I wanted to offer on my micro Sawppy rover ESP32 brain, I wanted to teach it to understand servo motor signals sent by radio control receivers. I found an example online of a project doing a similar thing and read through their code to see how they did it. Cross checking with Espressif documentation on ESP32's RMT peripheral, that precedent taught me how to configure RMT to read servo control PWM input.
When configured for signal receive, RMT watches the state of a digital signal and measure the time duration for on and off periods of signal pulses. There are a set of events that can be configured to trigger an interrupt service routine (ISR) for the application developer to pick up the measured data for processing. There are two challenges here. First is the general challenge of writing an ISR. There are constraints on what ISR code can do, and what APIs it can call. Violating such constraints will usually lead to mysterious hard-to-diagnose problems. The second challenge is specific to the ESP32 RMT peripheral, because the ISR code will have to know where to fetch data, which flags to check, and which flags to set or clear upon exit. Again doing this wrong usually results in weird behavior.
Fortunately, it's not absolutely required to write my own ISR. Espressif provides a default ISR for handling RMT events and parses data into a defined format and communicated via a ring buffer, an Espressif extension to FreeRTOS. Espressif's own RMT sample code to process infrared remote control signal uses this default ISR, and I decided to follow that precedent instead of diving into the challenge of writing my own ISR.
One of the RMT parameters is a clock divisor, stepping the default 80MHz sampling rate down to something that would work well for the signal at hand. I decided to use a divider of 80, so the signal is sampled at 1 MHz. This makes the math easier for me to understand in my head, because each RMT 'tick' is now one microsecond. The servo control signal pulse of 1 to 2 milliseconds maps cleanly to 1000 to 2000 microseconds. This is lower resolution than the divider of 10 used by my RC precedent, but it means I don't have to multiply everything by eight in my head and having one thousand levels of differentiation should still be more than enough.
My first draft didn't work because the default RMT ISR would never send data on the ring buffer. I know it is retrieving data and putting it on the ring buffer, because after a few seconds I would see "ring buffer full" error on the serial debug monitor. Eventually I figured out I misunderstood what "idle" meant in RMT documentation. I thought "idle" meant we lost the signal and need to go into some kind of recovery routine, so I set it to 32 milliseconds. This is wrong! "Idle" in this context actually meant the end of a single data signal of interest. Because servo control signals were arriving every ~16 milliseconds, the RMT idle threshold was never tripped and thus RMT thought data had not yet completed. The fix is to change idle threshold down to something far shorter than 16 milliseconds but comfortably longer than actual signal. Once I understood my problem and fixed it, RMT ISR started signaling data ready on the ring buffer, allowing me to retrieve servo control durations and translate that to my micro Sawppy joystick messages. Once that translation layer is in place, the little rover is no longer tied to a wired joystick!