This blog post assumes you know the basics of multiplexing and how an LED matrix works. The end result looks like this:
Let’s say you want to control an LED matrix. A plain boring 8*8 LED matrix you bought on Ebay a long time ago and is still somewhere in your desk. It’s pretty simple, after figuring out how multiplexing works you’ll have a smiley drawn on the LED matrix in no time at all. But it’s much prettier if the brightness of an LED matrix can be controlled via PWM, maybe make some nice animations as well. For an 8*8 LED matrix, 8 PWM outputs would be needed and you are good to go, something a bigger microcontroller generally has available. But what if you want a bigger LED matrix, 16*16 perhaps. And animations running smoothly at a high frame rate.
For an 8*8 LED matrix, to make it look smoothly, you need to multiplex the screen at about 100 Hertz minimum. So set a column, wait a short time, switch to the next column and set the value for this column, wait a short time etc etc. With 100Hz there is 10ms of time per screen, so about 1.2ms (10ms / 8 columns) per column. More then plenty to set some IO pins to show an image. For a 16*16 LED matrix this time halves to 0.6ms. PWM makes this a bit harder, as a whole PWM cycle must fit in this 1.2 or 0.6ms, meaning the PWM peripheral must run at 833 or 1666Hz minimum. This is all no problem for a PWM peripheral in a modern microcontroller.
But what if you don’t have 8 or 16 PWM pins? For example, an Arduino Mega has 12 PWM pins. ARM M microcontrollers like the STM32F103 have more PWM pins, but those are shared with other peripherals as well, so they might not all be available to use in a project.
An option is to bitbang PWM, toggle an IO pin quick enough to act as an PWM pin. This has the major downside of costing a massive number of CPU cycles. To get just 6 bit PWM, so 64 different levels of brightness, in the 0.6ms for an 16*16 LED matrix, the CPU must check 64 times if that IO has to be set high or not. This means that every ~10us the CPU has to handle IO. If you also want to calculate some animation to display, the CPU will be very busy.
This is where the DMA comes in. The DMA, or Direct Memory Access, is a peripheral available in many modern microcontrollers. Simply said, it’s a co-processor that can copy data from one place in the microcontroller to a different place without the CPU having to do a thing. It can be used to transfer an ADC value to a buffer in RAM and give a sign to the CPU when the buffer is full. The microcontroller I’ve used for this blog, the STM32F103, can transfer memory to memory, peripheral to memory and memory to a peripheral without the CPU doing a thing apart from setting up the DMA. Transferring data to a peripheral without costing CPU cycles. That sounds like a nice way of driving an LED matrix.