Using picojpeg library on a PIC with ILI9341 320×240 LCD module

5.00 avg. rating (94% score) - 1 vote

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.

See also

dsMP3, my LW/MW/FM/SW radio with MP3 recording/playback [Part 1 – Hardware Design]
dsMP3, my LW/MW/FM/SW radio with MP3 recording/playback [Part 2 – Firmware Design]

 

5.00 avg. rating (94% score) - 1 vote
ToughDev

ToughDev

A tough developer who likes to work on just about anything, from software development to electronics, and share his knowledge with the rest of the world.

12 thoughts on “Using picojpeg library on a PIC with ILI9341 320×240 LCD module

  • July 16, 2014 at 2:45 pm
    Permalink

    I ported your JPG decoder code to my PIC32 platform and it worked really well. Thank you! You saved my time. :)

  • July 16, 2014 at 2:49 pm
    Permalink

    Great! I am glad you find the jpeg decoder code useful in your project. Cheers :)

  • September 9, 2014 at 1:26 am
    Permalink

    Hello,
    I've been trying to figure out, using your jpeg_helper as an example, how to display jpeg images from a c file to the LCD and not from the SD card. But I'm getting lost with all these variables and especially with the pjpeg_need_bytes_callback() function.
    I'm using a hex editor to get the values and paste them to my source code as an array. Then I'm trying to pass those values to your pjpeg_load_from_file() function without any luck. Does this approach make any sense? Did you try to draw images this way first, before moving to the SD card? I'm not that experienced with all these, so I would appreciate any information or advice.
    Thank you

  • September 9, 2014 at 9:47 am
    Permalink

    Hi Manuel,

    Just a background question, are you attempting to use the library to draw JPEGs on a similar LCD, or to decode it and show via other means? If you're drawing on an LCD, have you made sure that you can draw simple text/graphics on the LCD first before attempting to draw JPEG?

    The current approach to decode a JPEG using the library is as follows:

    1. In the pjpeg_load_from_file function, call pjpeg_decode_init to start the JPEG decoder, passing in an empty image_info object (which will contain the image width and height and other information when the function returns), and the pointer to a pjpeg_need_bytes_callback function, which will supply the library with the JPEG image data as and when they are needed.

    2. In the pjpeg_load_from_file function, after the decoder has been initialized, call pjpeg_decode_mcu to decode each MCU – e.g. portion of the JPEG image – and perform the necessary tasks (e.g. drawing on LCD) until the image is completed drawing (status = PJPG_NO_MORE_BLOCKS) or if there are any other errors.

    3. When the library requires image data, pjpeg_need_bytes_callback(unsigned char* pBuf, unsigned char buf_size, unsigned char *pBytes_actually_read, void *pCallback_data) will be called, and your code is supposed to fill the pBuf buffer with the expected amount of JPEG data that the library requires. Typically, less than 512 bytes of data is required each time this function is called – this is done to save memory while on a PIC, otherwise the entire JPEG file has to be loaded to memory – something not possible on a PIC with limited memory.

    So to answer your question, you would only need to modify pjpeg_load_from_file to draw the decoded data on your LCD (or otherwise manipulate it) as needed. You would need to supply the image data, which you have converted to hex and stored in a c file, in the callback function pjpeg_need_bytes_callback. buf_size is the number of bytes that is needed for the next JPEG read and pBytes_actually_read is the number of bytes that your code actually returns to the library (pBytes_actually_read should be < buf_size). You would need to calculate these 2 variables based on the amount of data left in your JPEG data array after each read and set the correct value. The example code does so via g_nInFileSize (total file size of the initial JPEG file) and g_nInFileOfs (offset of byte in the JPEG file after the last read).

    I did not try to read JPEG from memory first as I got the code to read from SD card working after a few tries without much issues.

  • September 10, 2014 at 10:34 am
    Permalink

    Hello again,

    Thank you for your prompt reply. Even though I'm out of town and can't experiment at the moment I already feel it's more clear now. Yes, I'm using a similar LCD and already be able to draw bitmaps from both SD card and memory. My custom board is based on a Spartan 6 and it also has loads of available memory. Maybe I should just try to load the whole data stream at once.. I'll get back to you soon!
    Thanks again

  • February 5, 2016 at 10:02 am
    Permalink

    I have posted an Arduino port of picojpeg here:

    https://github.com/Bodmer/JPEGDecoder

    This may be of interest. Ana Arduino Mega (16MHz clock) decodes and displays a 320×240 image in 2.5s, an the Arduino Due (84MHz clock) does in in 500ms which is very usable.

  • February 5, 2016 at 10:04 am
    Permalink

    I have posted an Arduino library based on picojpeg here:

    https://github.com/Bodmer/JPEGDecoder

    This may be of interest. An Arduino Mega (16MHz clock) decodes and displays a 320×240 image in 2.5s, and the Arduino Due (84MHz clock) decodes the image in 500ms which is very usable.

  • June 12, 2016 at 9:03 am
    Permalink

    Ho, great job. I will try to port this file to CCS C Compiler. But there are many changes I need to make. First, the int variable is 8 bits. I want to sabe the jpeg hex file in a flash memory tha I have in pcb board I did with an Ili9341 lcd board. I want to show only 320×240 image with vertical orientaion.

  • June 12, 2016 at 12:45 pm
    Permalink

    Hope the code will help you. Let me know the progress of porting the code to CCS :)

  • August 26, 2017 at 2:39 am
    Permalink

    Please say me if can adapt the code for dspic33ep256mu806, and the price of this,
    renatomasias@yahoo.com
    regards

  • February 24, 2018 at 10:04 am
    Permalink

    HEllo,
    THanks for the help,i was in trouble,and you solved me!!
    for pic32,just some tricks:
    CKP=0; CKE=1;SMP=0;
    and for 2.8″adafruit v1.1,need to wire RT_CS to +3v3,if touch not used
    @80Mhz BRG at 3. less wont work
    !

  • ToughDev
    February 24, 2018 at 12:13 pm
    Permalink

    Thanks for sharing the tips to get the code to work on PIC32 :)

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>