Code Repo    |     RSS
MD's Technical Sharing



Thursday, January 9, 2014

Interfacing HY28A LCD module with ILI9320 controller and XPT2046 resistive touch panel to PIC microcontroller

This is a cheap 320x240 2.8" TFT LCD module that uses the ILI9320 controller for the display and the XPT2046 controller for the resistive touch panel. I purchased the module over a year ago but only had the opportunity to try it out recently. The module has since been phased out by the manufacturer and replaced with the HY28B that uses the ILI9325C controller.

Physical connections

The module has two 20-pin connectors on each side:

To my disappointment, the connectors on this module use 2mm pitch, and not the standard 2.54mm (0.1") pitch used by most hobbyist breadboards, sockets and connectors. I also tried to search eBay and could not find anything that might be useful, other than a few 6-pin connectors for the Zigbee module which also happens to use 2mm pitch, although I did find a photo here taken by someone who has jumper cables with small headers fitting the 2mm pin pitch of this module.

This is the time when some creativity is needed. Luckily since many of the parallel communication pins on the two 20-pin connectors on both sides of the module are not used in SPI mode, I am able to break some of the unused pins, leaving space for me to bend other pins and solder them to standard 2.54mm male connectors in order to fit a breadboard:


Interfacing the LCD module

Although the ILI9320 supports both parallel and serial communications, the HY28A module is configured to only use SPI. Using the example source code provided by the seller, I was quickly able to make this LCD show some text and graphics:


One interesting thing to note about the ILI9320 is that it uses SPI mode 3, not SPI mode 0 like many other SPI devices. This Wikipedia article has a good description on the different clock polarities and phases used by each SPI mode. From a PIC point of view, this means setting the correct value for bit 8 (CKE - Clock Edge) and bit 6 (CKP - Clock Polarity) in the SPI1CON1/SPI2CON1 register according to the datasheet:

bit 8 CKE: SPIx Clock Edge Select bit
1= Serial output data changes on transition from active clock state to idle clock state (see bit 6)
0= Serial output data changes on transition from Idle clock state to active clock state (see bit 6)

bit 6 CKP:Clock Polarity Select bit
1= Idle state for clock is a high level; active state is a low level
0= Idle state for clock is a low level; active state is a high level

For mode 0 (most SPI devices), you will need to set CKP = 0 and CKE = 1. For mode 3 (the ILI9320), CKP = 1 and CKE = 0.

Interfacing the touch screen

There are two variants of the HY28A module, one with the ADS7843 controller for the resistive touch screen and the other with the XPT2046 touch controller. The main difference is that the ADS7843 outputs analog voltages while the XPT2046 uses an SPI interface for the touch controller. My module uses the XPT2406 and has 5 pins:

TP_IRQ - Interrupt Request. Low when a press is detected.
TP_CS - SPI Chip Select
TP_SDO - SPI Data Input
TP_SDI - SPI Data Output
TP_SCK - SPI Clock

Like most other resistive touch controllers, the XPT2046 will return a raw coordinate value when a press is detected on the panel. For the coordinate to be useful, the code must convert it to a coordinate within the LCD resolution. To make things simple, the code can just look at the maximum and the minimum values that are returned when a press is detected on each corner of the panel and perform a linear conversion of the values to LCD coordinates:

// height and width of LCD 
#define MAX_X  240UL
#define MAX_Y  320UL  

// coordinates of sample touch points at 4 corners of touch panel
#define TOUCH_X0 255 
#define TOUCH_Y0 200 
#define TOUCH_X1 3968
#define TOUCH_Y1 3775

// calibration constants
cal_x = (TOUCH_X1 - TOUCH_X0) / MAX_X;
cal_y = (TOUCH_Y1 - TOUCH_Y0) / MAX_Y;
    
// get the raw touch coordinates
xpt2046GetAverageCoordinates(&tX, &tY, 5);

// convert to LCD coordinates
pX = (tX - TOUCH_X0) / cal_x;
pY = (tY - TOUCH_Y0) / cal_y;

Reading of the touch points can be done when TP_IRQ is low, indicating that a touch is detected. To reduce noises and achieve better accuracy, it will be better to perform several reads (5~10) for every press and calculate the average coordinate of the touched points. TP_CS must remain low when reading is performed, and set to high when reading is done. Coordinates reading must be stopped as soon as TP_IRQ is high, indicating that touch presses are no longer detected.

I was quickly able to prototype a program that allows me to draw on this resistive touch panel:


If you can't see it, the text reads "PPDS STMJ" and "BAHX MBA". The isolated drawing points are from the noises due to breadboard stray capacitance. A median filter can probably be used to remove these isolated points for better accuracy.

I also tried to connect the drawn points and achieved a better output:




The text reads "Hello ABC" in the first picture and "123" in the second picture. Ignoring the inappropriate connections between two adjacent characters (due to the inability to detect when the stylus is released from the screen to stop connecting points), the other problem is the zig-zag and not smooth shape of the drawing. This is probably because of the slow speed of the PIC24. At 16MHz SPI speed and 32MHz clock speed on my PIC24FJ64GA002, some precious time is wasted communicating with the touch controller, calculating the touched coordinates and plotting them. During this time, other points drawn by the user were lost and not plotted on the screen.

As it takes considerable time to read the touch points and plot them, it is not possible to migrate the entire process into an interrupt to increase touch sensitivity as an interrupt routine also needs to finish executing as fast as possible. The only solution for a smooth drawing would be a much faster clock speed, or perhaps to use Direct Memory Accessing (DMA), supported by this PIC. However, at this moment I do not yet have the time to explore either option.

Sample code download

C30 code for the ILI9320
C30 code for the XPT2046

Codes for both the LCD and the touch panel make use of my custom SPI library for the PIC24FJ64GA002 to facilitate SPI communication. Before working with the LCD or the touch screen, you will need to initialize the SPI modules using the spiInit method from my SPI library as shown below:

spiInit(1, 0b00011011, 0); // SPI Module 1 for Touch Screen, secondary prescale 2:1, primary prescale 1:1, SPI mode 0
spiInit(2, 0b00011011, 3); // SPI Module 2 for LCD, secondary prescale 2:1, primary prescale 1:1, SPI mode 3

Assuming that the PIC is running at 32MHz, the above code will set the SPI clock at 16MHz, fast enough for many purposes.
Read More »

Friday, November 15, 2013

Experimenting with ST7735 1.8-inch 128x160 color LCD on a PIC microcontroller

This tiny 1.8-inch LCD module is the second color LCD that I successfully attempted (the first is the Nokia 3510i LCD). The breakout board which I purchased from eBay also comes with an SD card socket:


 
The pinout for the board is as follows, inclusive of the SD card connections: 

1.  GND
2.  VCC 
3.  NC
4.  NC
5.  NC
6.  LCD RESET
7.  LCD A0 (R/S)
8.  LCD SDA
9.  LCD SCK
10. LCD CS
11. SD SCK
12. SD MISO
13. SD MOSI
14. SD CS
15. LED+
16. LED-


Interface via SPI

This LCD controller, ST7735, uses SPI for communication and requires just 5 data lines, namely RESET, A0, SDA, SCK and CS. Of particular note is the A0 line, also known as R/S, which indicates whether the bytes being transferred should be interpreted as command or as pixel data. Although SPI communication should preferably be done using the hardware SPI module (there are two on my PIC24FJ64GA002) for faster display speed, it can also be done via bit-banging if hardware SPI is not available. The following function will send a byte via SPI using software:

void write_spi_byte(unsigned char c){
    char x;
    for(x=0;x<8;x++){       
        LCD_SCK = 0;
        LCD_SDA = 0;
        if(c & 0x80)
        LCD_SDA = 1;
        LCD_SCK = 1;
        c <<= 1;
    }
}

I converted the Adafruit's Arduino library for this LCD to compile under Microchip C30 compiler for my PIC24FJ64GA002 and the LCD is able to draw some graphics nicely: 

Using the bundled SD card socket and Microchip MDD library, together with my custom 5x7 font, I was able to display some SD card information on the LCD: 

 

The board I purchased has an AMS1117 on-board regulator and expects at least 5V to be supplied to VCC to be able to generate 3.3V for the LCD and SD card to work. I did not know this and supplied 3.3V to VCC initially, only to find out that the SD card worked intermittently while the LCD still worked well. If you have problems with the SD card on this module, check if this is the case. 


Setting the color model

The ST7735 controller supports up to 262,144 (218) colors. However, to be able to use 262K colors, for each pixel, 18-bit of data have to be transferred via SPI. Since this increases the complexity. I have decided to stay with 65,536 colors (16-bit) colors, where pixel data can be transferred nicely just by using 2 SPI writes.  

In 16-bit color mode, the LCD expects pixel data to be in RGB565 format. The following will convert from the well-known RGB888 (24-bit color) format to RGB565: 

#define RGB565(r,g,b) ((((r>>3)<<11) | ((g>>2)<<5) | (b>>3)))   

An interesting point to note about this LCD is that there seems to be two variants with slightly different behaviors. If your module comes with a black tab, the BLUE and RED byte of each pixel will be swapped, resulting in the wrong color being displayed. If your module has a red or green tab, the byte order for each pixel will be correct.  

To fix this issue, you can change the above RGB565 macro to swap the red and blue byte, or you can change the value of the MADCTL register during initialization: 

writecommand(ST7735_MADCTL);

// R and B byte are swapped
// writedata(0xC8);

// normal R G B order
writedata(0xC0);


Downloads

Various bitmaps from my SD card as shown on the LCD:


The C30 source code for this LCD can be downloaded here.
Read More »

Saturday, October 19, 2013

Interfacing VS1053 audio encoder/decoder module with PIC using SPI

To fulfill my wish of building an MP3 player on top of a 16-bit PIC (PIC24FJ64GA002), I purchased a VS1053 audio encoder/decoder module from eBay. The VS1053, manufactured by VLSI, is capable of decoding WAV/MP3/WMA/OGG/AAC/MIDI files, as well as FLAC format with the help of a software plugin. It is also capable of recording audio, either via the Line In input, or via a microphone, which comes installed in the breakout board that I purchased.

Module pinout

The front and back of the module, with the 5x2 connection header: 


To be able to use it on a breadboard for prototype development, I made a small breakout board for the connection header using a small piece of stripboard. The pinout description for the connection header is below:

MOSI - Master Out Slave In
MISO - Master In Slave Out
SCK  - SPI Clock Signal 
XCS  - Send Command (Active Low)  
XDCS - Send Data (Active Low)
XRST - Reset (Active Low)
DREQ - Data Request

The module uses Serial Peripheral Interface (SPI) to communicate with the microcontroller. I used the code from here and adapted it to work with my PIC24FJ64GA002 hardware SPI modules.  The completed modified code can be downloaded here.

Audio playback

The VS1053 has a few registers which control its operations. Among them, the more important registers are:

SCI_MODE   - Controls the operation of the module
SCI_STATUS - Controls information on the current status of the module
SCI_BASS   - Controls the bass boosting DSP algorithm used for playback 
SCI_CLOCKF - Controls the chip clock frequency
SCI_VOL    - Controls playback volume 
 
To use the VS1053 module to play and record audio files, I converted the Adafruit's Arduino library for the VS1053 to compile under Microchip C30 compiler. To initialize the module, set the microcontroller SPI speed to 1MHz, pull down pin XRST for a short while to perform a reset and then read the value of register SCI_MODE, which should contain the default value of 0x4800:

// primary scaler 4:1, secondary scaler 4:1 @ 1MHz 
spi2Init(0b00010010);     

//Deselect Control
MP3_XCS = 1; 

//Deselect Data
MP3_XDCS = 1; 

// hardware reset
MP3_RESET = 0; 
delay_ms(10);
MP3_RESET = 1;
delay_ms(10);

//Set initial volume (20 = -10dB)
vs1053_SetVolume(volDefault, volDefault); 

//Read the SCI_MODE register
vs1053_ReadRegister(SCI_MODE);

When ready for audio playback, write to SCI_CLOCKF register to set the multiplier and increase the clock speed of the VS1053, followed by increasing the PIC SPI clock speed:

//increase the VS1053 internal clock multiplier and up our SPI 
vs1053_WriteRegister(SCI_CLOCKF, 0x60, 0x00); //Set multiplier to 3.0x

// Internal clock multiplier is now 3x.
// Therefore, max SPI speed is 5MHz. 4MHz will be safe.
// primary scaler 1:1, secondary scaler 4:1. 
SPI2CON1 = SPI2CON1 | 0b00010011;

The above code assumes that the PIC is running at 32MHz. When the setup is completed, supported files can be playing by simply sending data to the module via SPI:

void vs1053_play(unsigned char* data, unsigned int len)
{

    unsigned char *p;
    int i;

    p = &data[0]; // Point "p" to the beginning of array
    while(p <= &data[len - 1]) {
        while(!MP3_DREQ_IN) { 
            //DREQ is low while the receive buffer is full
            //You can do something else here, the bus is free...
            //Maybe set the volume or whatever...
        }

        //Once DREQ is released (high) we can now send 32 bytes of data
        MP3_XDCS = 0; //Select Data
        spi2Write(*p++); // Send SPI byte
        MP3_XDCS = 1; //Deselect Data
    }

    while(!MP3_DREQ_IN) ; //Wait for DREQ to go high indicating transfer is complete
    MP3_XDCS = 1; //Deselect Data
}


Except for a few problems with SPI setup on my PIC24, my completed code works upon first try. The played music sounds very clear and the output current is strong enough to be able to be listened through a headphone. Using Microchip MDD library, I was able to interface the PIC with my 128MB SD card and play WAV/MP3/WMA/OGG/AAC files from the card smoothly. For MIDI files, only midi type 0 (single track) files are supported by the VS1053. If your MIDI files are of type 1 (multi-track), you'll need to convert them to type 0 before they can be played. You can download many sample MIDI type 0 and type 1 files for testing here.

According to the datasheet, FLAC files are supported by loading a software plugin. I have not tried this, however.

Unexpected problem: floating XTEST pin!

In the midst of the experiment I suddenly realized that my VS1053 module stopped working. It could no longer play any audio file and SCI_MODE always read 65424 (0xFF90) upon power on, instead of the default 0x4800. When this happened, other SPI read/write operations still appeared to be working fine, e.g. written values could still be read back properly. Attempting to write the default value of 0x4800 to SCI_MODE was successful, but the module still could not play any audio data.

I decided to put the board away for a while, remove the power and try again. Surprisingly, everything worked again upon the first try, but only exactly once! Upon the second run the module exhibited the same beaviour (0xFF90 for SCI_MODE at startup) again. I tried again with a fresh VS1053 module and again, it could only work once.

What was exactly the problem? The module could not have been totally broken because SPI read/write was still working fine. And it was also unlikely that two different modules were damaged in exactly the same manner. At this point I suspected that since some parts of the code were incorrect, it could have altered the contents of non-volatile memory of the VS1053 and causing permanent damage. Searching the Internet revealed that other people have similar inconsistent behaviors and suspected non-volatile memory corruption of the VS1053. However, posts on the VSDSP forum confirmed that the VS1053 does not have non-volatile memory, so the problem must have been something electrical.

The real problem, after much research effort, is the XTEST pin on the VS1053. This pin must be connected to VDD for things to work consistently. Many clone boards, including earlier versions of the Sparkfun VS1053 boards and those sold on eBay, leave the XTEST pin unconnected, leading to unspecified behavior. I referred to the datasheet for the location of XTEST, which is pin 32 in the LQFP-48 package:


After several checks I confirmed that pin XTEST was indeed floating. Take note that since the PCB may be multi-layered, you can't judge whether a pin is floating just because it looks unconnected as it may be connected via the hidden layers of the PCB. Although the pins are tiny and appear very difficult to solder using a generic soldering iron, I was lucky as all I needed to do was to connect XTEST to VDD, which happens to be the neighboring pin 31 (CVDD3). A single drop of solder between the two pins is all that is needed and the modified board is below (the soldered pins are in red):



With this modification, the board works well and there are no longer any intermittent problems. If you still have problems, make sure that GPIO0 and GPIO1 pins are also connected to GND. Some boards have GPIO0 connected to GND while GPIO1 connected to VDD and the module will boot up in real-time MIDI mode instead.

Audio recording

The module supports recording, either via the on-board microphone or via the Line In input. Natively, Pulse Code Modulation (PCM) encoding is supported, in either linear (uncompressed) or ADPCM (compressed). Encoding into OGG format is supported via software plugin. PCM are encoded in 16-bit little endian format.

To enter recording mode,  set the bit SM_ADPCM and SM_RESET of the SCI_MODE register. Bit SM_LINE1 may be set if recording via Line In, otherwise clear it to record via the microphone. Page 53 of the datasheet explains this in details. Take note that if you're recording via the microphone, set SCI_AICTRL3 bit 0-1 into left channel mode, otherwise the default is stereo (both channels) and the output audio will be distorted since the microphone provides single channel (mono) only.

I tried to perform a linear PCM recording and write the data to an SD card. At 8kHz and 16-bit, the output file consumes approximately 16KB per second. I performed a benchmark of the FSfwrite function of the MDD library by opening a file and appending 0xFF to it. The speed is approximately 18KB/s with the PIC running as 32MHz. The actual writing speed when recording will be lower as the PIC is also busy communicating with the VS1053 to retrieve the audio data. As a result, the recorded audio file appears to be choppy and it seems further optimization is needed for recording to work properly.

As this is a hobby project, I decided not to proceed with improving the recording performance. Using this module for audio playback should be good enough on a PIC24. The C30 code to play audio files using VS1053 can be downloaded here for those who are interested.

See also

LD3320 Chinese Speech Recognition and MP3 Player Module 
Read More »

Wednesday, March 23, 2011

Interfacing Nokia 3510i and 5110 LCD with PIC Microcontroller

Recently I started to regain some interest in embedded systems, and start to experiment with PIC micro-controllers. After some successful attempts with standard character LCDs using using the HD44780 controllers, I decided to get some Nokia LCD modules from eBay to explore.

The 2 LCD modules I purchased are for the 3510i and 5110 models. Both have built-in controllers which use Serial Peripheral Interface (SPI). The following are the pinout for my modules, notice that pin assignments may vary slightly.

LCD pinout

Nokia 5110:



Nokia 3510i:

The only different here is pin #5 which is used as data/command selection for the 5110, and unused for the 3510i.

Voltage difference: 5.5v vs 3V

Both LCDs are designed to work with 3.3V, but due to an internal voltage clamp 5V can be used for SCLK, SDATA, REST, D/C and CS as long as a current limiting resistor (around 10k) is connected in series for each line. 3.3V should still be applied to VCC and the LED supply. I have tried using voltage dividers, which did not work, perhaps due to the LCD varying internal resistance and current consumption.

With the above connections we can only write to the LCD but can't read back the LCD response because 3.3v is not high enough to register as logic '1' in the PIC. Luckily reading from the LCD is not required for basic operations; all that is needed is sufficient delay after each operation to make sure the LCD is ready for next command.

I have chosen the PIC16f88 simply because it's available in my junk box. For simplicity, I have decided to use bit-banging to send data, and not the PIC built-in SPI module. Although this usually means complicated code and lower throughput, it does not matter as all I wanted is to get the LCD to display something useful ;)

LCD Memory Map

The 5110 LCD is monochrome, uses the PCD8544 controller and has a resolution of 48 rows × 84 columns. Each 8 pixels on a single column consumes a single byte on the LCD memory map. It takes 504 bytes to fill the entire LCD.

The 3510i LCD has 97x66 resolution and can operate in either 256 or 4096 colors. Since there seems to be little difference between 256 and 4096 colors due to the small resolution, I have chosen 256 colors for simplicity. Each pixel on the LCD is represented by a single byte and filling the entire LCD takes 6402 bytes in 256-color (8-bit) mode.

Sample code: displaying test patterns

The following code shows how to display all black pixels on the Nokia 5110 LCD. Notice that LCD initialization code is not shown.

void lcd_5110_clear()
{
    for (int i=0; i<84;i++)
    {
        unsigned char row;
        for (row=0;row<6;row++)
        {
            //all black pixels
            char data = 0xFF;
            
            lcd_5110_send(0x40 + row,0); //Y address
            lcd_5110_send(0x80 + i,0);   //X address
            
            //write to display memory
            lcd_5110_send(data,1);
        }
    }
} 


The following code shows how to display a selected color on the 3510i LCD:

void addset(unsigned char x1,unsigned char y1,unsigned char x2,unsigned char y2)
{
    send(0x2a,0);//column address set
    send(x1, 1);
    send(x2, 1);
    send(0x2B, 0);//page address set
    send(y1, 1);
    send(y2, 1);
    send(0x2C,0 );//memory write
}
void LCD_Clear(unsigned int value,unsigned char Color)
{
    unsigned char x, y;
    addset(0,0,97,66);
    for(y = 0; y < 67; y ++)
    {
        for(x = 0; x < 98; x ++)
        {
            send(Color, 1);
        }
    }
} 


Displaying text and graphics

Up until now you can only display test patterns on the LCDs. The use of a bitmap font (and extra code) is required if you want to display any useful text. I have chosen a 8x12 font for the 3510i LCD, and a 5x8 font for the 5110 LCD. The font, together with any graphics to be displayed, will be stored in a 24C64 (8Kbytes) I2C EEPROM. To program the EEPROM, I use the I2C version of the PonnyProg programmer. Notice that this may not work on newer PCs where the available current from the serial port is limited and will never work with a USB-to-serial converter. In my experiment, I made a stupid mistake of adding a LED via a 470 ohm resistor to show activity during programming. This result in data corruption and verification errors after programming due to excessive current consumption. Changing the resistor to 2k worked fine, although the LED is much dimmer.

With the EEPROM to store font and graphics, the 5110 LCD could now display text and some monochrome bitmap:


The 3510i LCD could do a much better job ;)


Notice that the serial port connector is for debugging purposes only.

The entire source code is attached here. The contents of the EEPROM is included with the source code and named eeprom.bin.

See also:

ST7735 1.8" 128x160 color LCD
ST7920 128x64 graphical LCD
Other LCD modules that I have interfaced
Read More »