Well it seems all of my projects recently have been based around my LCD monitor! I think its just because its fun to actually be able to see the outcome of your work as opposed to just seeing a statement change in debug or an LED flash. This isn’t actually an original idea of mine, I got the inspiration from here. Regardless, I decided to have a go at a scratch implementation – which is pretty much the same as theirs but hey! If it ain’t broke, don’t fix it.
It’s a really clever idea of using the SPI peripheral to shift out bits for each line, producing a 1bit monochrome scheme. As everybody else has gone for green, I thought I’d join them and stick to green too. I’m using a simple VGA connector eith wires soldered on, 3x 220ohm resistors and 3 pins on my STM32F4 discovery. It works by synchronising the master clock to a multiple of the pixel clock. In this instance, I’m using the 800×600 @ 60Hz specification found here. The main limitation here is the SPI peripheral not being fast enough, therefore, this can be used to calculate the required clock speed. The SPI peripheral is connected to the APBx bus which on the STM32F4 series, has a maximum frequency of 84MHz. The APBx clock is derived from the master clock divided by 2. The smallest SPI prescalar is also 2, therefore for a pixel clock of 40MHz (as per the specification), I’ll need a master clock frequency of: 40*(Minimum SPI Presc)*(APBx Clock divider), which in this case is: 40*2*2 = 160MHz. The STM32F4 discovery is specced up to 168MHz, therefore this is a realisable clock! With PLL settings of /1, x320 and /2, this clock can be achieved with a standard 8MHz crystal. Using the STM32F4 clock generator spreadsheet, a C startup file can be generated which can be used in your own program.
The next easiest thing to do is to clock the horizontal count timer from this same pixel clock, meaning the values from the specification correspond to an equivalent timer count. In my application, I’m using timer 1 for my horizontal count timer and this is driven off the main system clock. Therefore, to get the timer clocking at the pixel clock rate, a prescaler of 4 is required. As the STM32F4 is a feature full device, it gives the option to use certain timer 1 outputs as the clock for other timers. This is a really useful feature as this allows the horizontal counter to create a clock pulse after every line. After having a quick skimread of the artekit, I think I’ve done my timings differently. In my application, the zero count of the timer is actually the beginning of the sync pulse. The HSync output then goes low for HSync sync pulse amount of counts (128 counts, for this specification). The counter then has an interrupt after HSyncPulse+HSyncBackPorch amount of counts (minus a few counts to compensate for interrupt overheads). In this interrupt, the SPI output is re-enabled (I’ll explain why later…) and the DMA stream is started, after setting the current position to start the stream from in the graphics buffer. After every line, the graphic buffer position is incremented by XPix/16 (the amount of words in a line). This gets the DMA ready for the next transfer. Timer 1 also has an addition capture compare which is used to disable the SPI output and set it low. This capture is set during the front porch (which is actually near the end of the timer period, HSyncPulse+XPix+HSyncBackPorch+zzz counts, where zzz are a few extra counts for overheads) and sets the GPIO Pin corresponding to the SPI peripheral data output to a standard GPIO output as opposed to an alternate function output. The GPIO register entry for this pin is initialized with 0 at the start of the sketch. Finally, a capture is also present at exactly HSyncPulse+XPix+HSyncBackPorch counts. This is what is used to clock the VSync timer, timer 2.
Timer 2 has the relatively simple job of producing the VSync pulse. This is done at the start of the timer count, like with the HSync timer too. There is also an interrupt on timer 2 to signify the end of the frame. At this point, the graphic buffer pointer is returned to the beginning of the pointer and the graphic buffer position counter is reset to zero.
~300 or so lines of code later and voila! A working 1bit monochrome VGA controller. As ever, I’ve used a landscape test image to test the quality on my screen. The image has been dithered using matlab to give a nicer effect compared to purely thresholding it to on or off.
As you can see, text can be written to the screen! As the graphics buffer is stored in memory, it allows me to use my standard GFX library for all graphics functions. I can use the LCD as if it was a simple PCD8544 equivalent, just loads larger! The one difference is the bytes are packed horizontally instead of vertically meaning a small change to the WritePix function. The image was dithered with in Matlab, packed into the same image format that the display requires and stored as a constant array of bytes. The array is then copied directly into the graphic buffer at initialization and text is written over the top.
As ever, you can find the code on my Github!