Using a DMA capable SPI as an LCD controller


Nowadays graphical displays are used everywhere in embedded systems, replacing old character displays, as well as seven segment displays. When it comes to interfacing these displays to microcontrollers, it is clear that they can be divided in two categories, those that have an integrated controller, and those that don't.
Especially among hobbyists, the ones with embedded controllers are the most widespread, for their ease of use. In fact, these displays have an embedded RAM memory, and the microcontroller needs to send data to them only when changing what appears on screen. The interfaces are usually parallel, but the lack of strict timing requirements allow bitbanging via GPIOs. Sometimes, especially for smaller screens, there are even serial interfaces, such as SPI. Essentially, the complexity of these displays is all in the software that needs to be written to make them turn on, display graphics and text, etc.; hardware is almost trivial.
On the other hand, displays without an embedded controller needs to be continuously refreshed to work. Even if the image on screen doesn't change, the same pixel data has to be sent over and over, around 60 to 100 times a second to provide flicker-free operation. This makes it difficult to interface them to microcontrollers.
In this article an innovative approach at dealing with this issue is presented, using a DMA capable SPI peripheral, together with some glue logic.

Hardware used for the tests

This is the display used for the tests, an Optrex DMF-682, which is a monochrome LCD with a resolution of 256x128 pixels.
Despite being physically organized as 256x128 pixels the display is electrically organized as a 512x64 one. The physical layout is obtained by splitting the 512x64 display into two 256x64 parts, and stacking one atop of the other.

As can be seen from the image of the PCB, it has nothing more than row and column drivers, as the datasheet confirms. The display was given me by friend of mine, nicknamed Otacon22, who asked me to try and make it work as it would be a perfect fit for the control panel of the BITS system, an automation system for our LUG, the POuL.
The chosen microcontroller for the application this display is going to be used is an STM32, running the Miosix kernel, and the STM32, despite having a rather broad range of peripherals, lack an LCD controller.

Refreshing a screen through SPI

The display's datasheet explains the hardware interface to make it work, which is rather standard for those displays. There is a 4bit wide databus for sending pixel data, a pixel clock, an horizontal synchronization input, called LP, to be pulsed at the end of each line, and a vertical sync input, called FLM, to be pulsed each frame. There is also another input, M, that needs to be toggled at each frame. By being a monochrome and not a greyscale display, the 4bit databus carries four pixels at a time.
The issue with this interface, is the speed at which it has to be operated. The datasheet specifies a typical period of 0.223ms to send a line. Given that a line has 512 pixels, but they are sent four at a time, the pixel clock has to be 574KHz. Of course, the display would operate also with a lower (or higher) frequency, but reducing it too much would cause visible flickering.
Now, the first approach one may think is bitbanging via GPIOs. Implementing the logic of this interface in code is easy, by sending out pixels four at a time and keeping a counter to know when to pulse vsync and hsync, but the problem is in doing it at the required speed. Even assuming code can be optimized to work at this frequency, the microcontroller would spend all of its time refreshing the display, without doing anything else, and this is ofcourse unacceptable. Also moving the display logic in an interrupt is prohibitive, as the interrupt would need to be called 574000 times a second, way too much.
Luckily, the STM32 microcontrollers have a DMA, which seem the perfect fit for this application. A DMA is a peripheral that is designed to transfer large amount of data, either from memory to memory, memory to peripheral or peripheral to memory, without CPU intervention. In this case the desired data direction is surely memory to peripheral, but the issue is, which peripheral? The SM32 has no peripheral capable of connecting directly with the display, so it is necessary choose the closest match and adapt it with some glue logic. This closest match is surely the SPI bus, which has a clock and a data output, and shifts data out serially. Unfortunately, the display has a 4bit parallel interface, so it is required to partially deserialize the data stream before feeding it into the display. The next image shows the designed circuit.

The MOSI and SCK lines from the microcontroller's SPI bus first pass through pull-up resistors (they are configured as open drain) to translate the logic level from the 3.3V the microcontroller operates at to 5V, which is the voltage required by the display.
Then they are connected to an 74HC4094 which performs the pixel deserialization, and the four outputs are connected to the pixel databus. The display also requires a clock signal, and the SPI clock is not suitable, as it is four times faster than the required one (due to the deserialization). Therefore, a 74HC4040 is used to divide the SPI clock by four. The SPI is configured to operate at 3MHz, which divided by four results in a 750KHz pixel clock, which is close enough to the 574KHz, and also has the advantage of being higher, so there are no worries about flickering. Note that the 4094 and the 4040 have been carefully chosen as the 4094 outputs change at the rising edge of the input clock, while the 4040 at the falling edge, so that the pixel clock transitions when the pixel data is stable, avoiding setup and hold timing issues. Also, the 74HC4xxx versions of these ICs is recomended, for their higher speed. Unfortunately, I had only the lower speed CD4xxx lying around, and beacuse of that my timings are not completely right, the pixel clock rise and fall time constraints are not met, even though the display works.
Now, the 4040 has many outputs, each of which equals the previous one divided by two. And given that a line of the display is of 512 pixels, which is 2^9, it is possible to generate also hsync in hardware using an appropriate output of the 4040, Q9. Now, the hsync signal should be briefly pulsed at the end of each line, so an RC circuit is used to generate that pulse. The trimmer R4 is used to tune the pulse width, but the value is not so critical, anything from 1K5 to 2K7 is good, so it was replaced by a fixed resistor with a value in the middle, 2K2. Keep in mind that this will likely vary from display models, so if a display different from the DMF-682 is used, some tweaking is still required.
The last two signal, FLM and M, are generated in software in the interrupt that is fired at the end of each DMA transfer. Now, with a pixel clock of 750KHz the display does 90 frames per second, so the end of DMA transfer interrupt is called only 90 times a second. The resulting design therefore sends out an entire frame in hardware, and only requires the CPU to execute an interrupt routine at the end of each frame, resulting in the CPU being free to execute application code most of the time.
A test code has been written for an stm32vldiscovery running Miosix to prove that this setup is feasible, providing good results. These images show the generated waveforms.

The first image also shows the rise and fall time of the pixel clock, which are out of spec with respect to the 50ns maximum specified in the datasheet. Anyway, the display works.
As a test image a dithered version of the POuL logo was used, and is shown in the following images.


This can definitely be considered a proof of concept of the feasibility of driving a framebuffer-less LCD using a DMA capable SPI. Source code is available, even if it is just a test code, that still needs optimizations. In the long term, I'll probably integrate this code into the mxgui library to have ready made graphical primitives to draw text and images.