This is a bit of a no-brainer, whilst their are numerous libraries that support emitting PWM signals to Pi GPIO pins, none of these scale terribly well both from an available pin count nor CPU resource perspective, so using some sort of external bus connected device seemed to be the obvious choice.
A popular chip which is available is the PCA9685: 16-channel, 12-bit PWM Fm+ I²C-bus LED controller, specsheet here. This is a very flexible chip designed to sit on an I2C bus – Inter-Integrated Circuit, running at up to 1MHz.
12 bit output gives 4096-bit resolution of pulse widths from 0 to Full on
Funky sequencing options can be used to stagger each LED ‘on’ pulse to minimise overall power demand when operating at less that full power
Up to 62 of these 16 channel chips can be connected to a single I2C bus
Only downside for a hobbyist is that these devices are packaged either in TSSOP 28 pin or HVQFN28 packages, the former having lead-out pin spacings of .65mm, and the latter surface mount, so neither are suitable for hand soldering, however there are many manufacturers of ‘daughter boards’ upon which these chips are pre-mounted and the connections brought out to more reasonable 0.1inch pitch pinouts, and at a reasonable price point of ~$2.50 each
The Pi model-B has 2 I2C interfaces however in reality only one is available for general use since the other is interrogated at boot time for EEPROM’s indicating the presence of Host Attached on Top – HAT daughter boards, however with a possibility of addressing 62 x 16 channel PWM’s on one bus that should be more than enough for my purposes!
The I2C bus is a simple 2-wire bus consisting of a Serial Data Line and a Serial Clock Line abbreviated SDA and SCL respectively and is commonly used for inter-chip comunications – anywhere where speed is unimportant (the Systems Management Bus -SMB on enterprise class motherboards is a subset of I2C) and for our purposes running at 1MHz clock should be more than quick enough.
The diagram below shows the wiring setup –
The PCA9685 has 5 I2C address pins which on the daughter board I’ve used (a cheap Adafruit clone) are by default biased towards Vss so by default the device is configured to address 0.
Below is some quick basic code to tie this all together – the Rotary Encoder with zmq publisher sending the decoded output via the IPC channel
Below the ZMQ subscriber receives the message and after a basic sanity check and accumulator calls the PWM device driver to adjust the appropriate channel pulse width.
Here is a scope capture of a single switch rotation by one detent, because the SPI bus that the GPIO expanders sit upon is running at 10Mhz, whereas the I2C bus upon which the PWM devices sit is only running at 1Mhz you can’t make out the individual bus decodes, however the data is captured in the serial decode window.
The red scope line shows the PWM signal out of the PCA9685 channel and the cursors indicate that the end to end latency is sub 5ms, so well below the realms of human perception!
So we have the rudiments of a working system –
We can capture and decode the rotary switch getting the direction and increment count and send that via zmq to another python instance which takes the message and sends the appropriate control command to the respective LED channel.
The fact that we have decoupled the reading of the gio’s from the pwm code means we can continually service interrupts and hopefully not miss any and let the zmq subsystem queue if necessary any output commands to the pwm chips.
This output code is presently quite small, however as we’ll see there are numerous areas of functionality that need to be added to make it really useful, and that added complexity may well impact the overall latency of operation.