With my main scale model receiver PCB (BLE-RC) having too few PWM pins, I designed an expander PCB to be used for extra outputs. This is is expected to be used in the same model as pin expander for BLE board, or be placed in a detachable trailer and control trailer functions with commands coming from the truck. The PCB is still required to be as small as possible, current iteration is 20x10mm.

The expander has 4 motor drivers (H-bridges) and 7 PWM outputs to control LEDs. It is based on PY32F003 MCU due to it being one of the very few ARM MCUs capable of running on 5V, which allows me to eliminate linear regulator, run the MCU directly from Li-Ion battery and raise voltage applied to LEDs (for LEDs that have high forward voltage).

It’s all good, but the problem is that this MCU in general and QFN20 3x3mm package specifically does not have enough timers and PWM channels to power all the functions.

Current implementation utilizes 9 PWM pins, of which 6 pins are shared between H-bridges and outputs, and 3 pins have dedicated functions (i.e. only connected to output pads or to H-bridges).

Ideally number of independent functions should be maximized. Here are some ways I considered.

Switching between non-inverting and inverting timer outputs

Some timers of the MCU are so-called Advanced timers and support 2 complimentary outputs on one PWM channel. This is designed to ease control of 2 sides of a half-bridge with one PWM timer channel. We don’t control MOSFETs directly, but having 2 possible pins on one channel can be beneficial for our motor control as well. Usually, one H-bridge is connected to 2 PWM pins and 2 timer PWM channels. However, when driving an H-bridge with PWM, one pin is always low, while another has PWM applied to it (and to change direction, pin functions reverse). So if we connect H-bridge inputs to complimentary pair of outputs of one PWM channel, we can alternate PWM between one of 2 pins by switching pins' GPIO mode from simple output to PWM (Alternate function, to be precise).

Looks that with this option, 12 PWM pins are possible (not at once), of which 6 are complimentary pairs, which makes it possible to control 3 motors independently and have 6 pins for the rest of the tasks. Or, when motors are not in use, 9 pins can do LED control.

DMA controlling GPIO and Binary code modulation

Explanation of binary code modulation (BCM) can be found, for example, here: https://www.batsocks.co.uk/readme/p_art_bcm.htm.

BCM replaces PWM and allows to output 2^N different values on a pin (not in precise voltage, but averaged over a period, same as in PWM), by making only N writes to the GPIO output register. It writes N bits and every bit is retained on the pin for the amount of time proportional to its binary value, e.g. first bit is held for one time slice, and seventh bit of 8-bit value is held for 128 time slices. The timing of GPIO updates does not depend on values to be written to the pins, which makes it very attractive when a lot of pins need to be updated at once.

The idea is very simple: 2 DMA channels are configured. Both channels take requests from timer update event (TIM1_UP, for example). One DMA channel updates timer period (TIM1→ARR). Periods are taken from RAM array of size 8 and are pre-calculated to be 1,2,4,8,…​ Second DMA channel writes values to GPIOA→BSSR, updating pib values. Values are taken from RAM array of size 8.

BSSR register is used instead of OUT so that pins not touched by this mechanism can still be utilized by other means (from CPU).

As I haven’t gotten to this point, it is possible that one event source cannot trigger 2 channels DMA. The contingency plan is to use timer UPDATE event for one channel and CC event for another, configured to trigger with same frequency.

Alas, it turned out this is not possible with PY32, while should work on STM32F series. Cortex M0+ core is different from M0 in GPIO implementation, on M0+ the GPIO is connected directly to the CPU, which makes it faster, but not available to anything else, including DMA.

This affects not only PY32 chips, but also newer M0+-based STMs, e.g. STM32G030 that I used in STM-based motor board.

This trick most probably works on older STM32F-series, as I’ve seen examples where DMA writes to GPIO register successfully. However, I don’t have plans to use STM32F chips at the moment.

As an exercise, I tried to make it without DMA by updating timer period and GPIO register in a timer interrupt. The result cannot work at very high frequencies, presumably because LSB timer interrupt is lost while previous ISR is running. Highest frequency I tested was 500Hz, which is ok for LEDs, but too low for motors.

Bit-banging pulse density modulation

Here is a description of modulation method: https://en.wikipedia.org/wiki/Pulse-density_modulation.

Compared to PWM, implementation of PDM offers visibly higher refresh rate with the same update frequency, so refresh rate can be lowered, possibly to a level where bit-banging is viable. Efficient implementation for several pins (combined writes to GPIO register and skipping unnecessary loop iterations) should be somewhat simpler than for PWM, but that’s untested.

I have an implementation of this method for nRF52 (BLE-RC main PCB), but feasibility still needs investigation.