I purchased a 320×240 LCD module which supports 320×240 resolution and comes with an SD card slot from eBay:
This LCD is using the ILI9341 controller supporting SPI mode. Within minutes I was able to sketch a program which draws text and graphics on this LCD without difficulty based on the sample code provided by adafruit:
Since the LCD resolution is high, I decided to attempt something which I have never done before, and which many hobbyists consider a great challenge on this 16-bit micrcontroller: decoding and displaying JPEG images from the SD card.
Finding a JPEG decoder library
The first candidate that came to my mind was the Microchip Graphics Library, specifically built for 16-bit and 32-bit PICs, as I have good experience with their Memory Disk Drive library, which is very robust and capable of handling various file systems. However, a quick look at the files after download revealed that things are not so simple – the library sample application is made to work with various PIC families and is designed to read graphics images from certain flash memory chips and display them onto a few supported LCD displays. As my ILI9341 is not supported, I figured that it would be a challenge to clean up the code just to get the part that I wanted, and decided to find a cleaner JPEG decoder library.
With some research, I chose picojpeg, an open source JPEG decompressor written in C in a single source file with specific features optimized for small 8/16-bit embedded devices. After getting the sample application (which converts JPEG to TGA files) working using Visual Studio, I proceeded to port the library to C30.
Porting picojpeg to C30
The library consists of just 2 files, picojpeg.c and picojpeg.h which use standard ANSI C and should compile under C30 with no issues. However, the sample application, jpg2tga.c which contains example code to use the library to decode JPEG, is written with Windows and Visual Studio in mind and will need adjustment to work under C30. Specifically, declarations with int, long and similar data types will need to be modified as int on Windows defaults to 32-bit whereas it is 16-bit in C30. Also, since right shifts under C30 are always unsigned, the following preprocessor will need to be declared and set to 1, as commented in picojpeg.c, otherwise the colors displayed will be wrong:
// Set to 1 if right shifts on signed ints are always unsigned (logical) shifts
// When 1, arithmetic right shifts will be emulated by using a logical shift
// with special case code to ensure the sign bit is replicated.
#define PJPG_RIGHT_SHIFT_IS_ALWAYS_UNSIGNED 1
By adapting the code from the jpg2tga sample application, I wrote a helper file, jpeg_helper.c, with the following function to read a JPEG file from the SD card and draw on the LCD.
JPEG_Info pjpeg_load_from_file(const char *pFilename, int reduce, unsigned char showHorizontal)
Pass 1 to showHorizontal to display the image in landscape mode on the screen. Pass 0 to display it in portrait mode.
As image data in a JPEG file is internally stored as a number of relatively small independently encoded rectangular blocks, usually 8×8 or 16×16, called Minimum Coded Units (MCU), one does not have to read the entire JPEG file into memory before displaying it. Therefore, even with the limited memory of a PIC, it is possible to display big JPEG files (subject to file system size limitation and LCD resolution) on the LCD by reading data and decoding them as the image is being rendered. This also makes it possible to load a scaled-down version of a high resolution JPEG file by simply rendering the first pixel of each MCU block, instead of the whole block. To display a scaled-down version of the image, pass 1 to the reduce parameter.
For simplicity, the jpeg_load_from_file function does not handle grayscale JPEG files.
With the above changes, I managed to use the picojpeg library to display a 320×240 JPEG on the LCD. At 32 MHz clock speed on a PIC24HJ128GP202, it took 10 seconds for the PIC to finish reading the image data from the SD card, decoding the image and display on the LCD. The process is shown in the following video.
The original photo can be downloaded here.
In my test, by plotting only the first pixel of each MCU, on the same PIC configuration, a 2816×2112 (2.41MB) JPEG file finished rendering on the 320×240 LCD in 105 seconds with no issues.
Overclocking the PIC
Although it is amazing to me that a 16-bit micro controller at 32 MHz is able to render big JPEG files, the speed (10 seconds for a 320×240 image and 105 seconds for a scaled-down display of a 2.41MB image) is too slow for any practical purposes. For a faster rendering speed, I decided to operate the PIC at a faster clock speed. Still using the internal oscillator, this is done by increasing the frequency multiplier:
// Using internal oscillator = 7.37MHz // Running Frequency = Fosc = 7.37 * PLLDIV / 1 / 2 / 2 // Output at RA3 = Fosc / 2 CLKDIVbits.FRCDIV = 0; // FRC divide by 1 CLKDIVbits.PLLPOST = 0; // PLL divide by 2 CLKDIVbits.PLLPRE = 0; // PLL divide by 2 PLLFBDbits.PLLDIV = 15; // Freq. Multiplier (default is 50)
According to the datasheet, the PIC24HJ128GP202 can run at a maximum of 80MHz @ 40 MIPS, by setting the multiplier to approximately 43. During my experiment, the PIC still seems to run at 100MHz and is able to do simple UART communications, although the device would get slightly hot. Above 100MHz and up to 120MHz issues start to arise, for example, program would terminate unexpectedly with MPLAB reporting “Target Halted.”. By opening View > File Registers and examining the RCON register at address 0740, it looks like a brown-out reset has occured (bit 1 of RCON is set, sometimes bit 0 is set as well). On the PIC24HJ128GP202, there is no way to turn off the brown out reset feature – it is unconfigurable. Above 120MHz, MPLAB would not even successfully start debugging the program on the PIC using PICKit2.
PIC clock speed vs. SD card SPI speed
At high clock speed and with the internal oscillator, there will also be problems of selecting the correct BRG value for the UART baud rate – in fact when testing at 100MHz, I could only get UART to run at 9600bps! As UART is mostly used for debugging in my case, this should not be an issue. Another greater issue is with the SD card SPI clock speed as many older SD cards support up to 20MHz only but the MDD library by default runs the SD card SPI clock at 1/4 of of the PIC clock speed. This is seen in the SYNC_MODE_FAST declaration in SD-SPI.h:
// Description: This macro is used to initialize a 16-bit PIC SPI module #ifndef SYNC_MODE_FAST // primary precaler: 1:1 secondary prescaler: 4:1 #define SYNC_MODE_FAST 0x3E #endif
This means that even at just 80MHz PIC speed, the SD card SPI speed would be at 20MHz – reaching the maximum supported speed of some cards. To work around this, the SPI pre-scalers would need to be changed to 8:1 to reduce the speed to just 10MHz:
#define SYNC_MODE_FAST 0b111010
This reduces the SPI speed by half, making reading of SD card data and rendering of the image slower, defeating the purposes of overclocking.
In my tests, even at just 64MHz, intensive reading of JPEG data from the SD card would fail randomly and unexpectedly if the circuit is built on a breadboard. Migrating to a strip board fixes the issue and allows the clock speed to be increased. I attributed it to stray capacitance on the breadboard which becomes a problem as the SD card SPI frequency increases. In fact, 32 MHz is the maximum speed at which I could get the circuit running reliably on a breadboard.
The MPLAB source code of the ported picojpeg library can be downloaded here.