PMP and LCDs on the PIC32MZ

Using the Parallel Master Port (PMP) to drive an LCD

OK, finally have my latest development board soldered and working. Today I will talk about using the PIC32MZ to drive an LCD. In my case, the LCD is a 7" 800x480 LCD with a SSD1963 LCD controller chip embedded in it. I got it from BuyDisplay, if anyone's interested. In short, today I want to show you how to do this:

PIC32MZ - Using the PMP to drive an LCD

(Although hopefully you will have more sense than me and take the ugly screen protector off your LCD :) )

The Parallel Master Port (PMP) peripheral

So what is the PMP? Basically, it's an easy way to read or write lots and lots of data quickly to an external device. It can run in either 8bit or 16bit mode and supports up to a 16bit address so it's very useful for things like LCDs, cameras and external memory.

Important Note: In my example I'm using a separate Chip Select line and not making use of the internal PMCS1 and PMCS2. This is purely due to design limitations caused by the widely spaced layout of the PMP pins on the PIC32MZ.

<TL;DR>
But how is it different from just writing 16bits of data to LATB? The main difference is all the control signals that come with the PMP. The PMP provides two Chip Select (PMCS1 and PMCS2) lines, a Read Strobe (PMRD) and a Write Strobe (PMWR). This turns code like this:

// Send data
LATB = data;
// Toggle clock signal
LATASET = 1;
LATACLR = 1;
// Wait for LCD to be ready again
while (PORTAbits.RA1 == 1);

into this:

// Send data
PMDIN = data;
// Wait for LCD to be ready again
while(PMMODEbits.BUSY);

When you're talking about LCDs, like my 800x480 one, not having to manually handle clock signals can turn into a massive difference in throughput.
</TL;DR>

OK, let's get on with it. The important pins for PMP are:

  • PMD0 to PMD15 - The data pins
  • PMA0 to PMA15 - The address pins
  • PMCS1 and PMCS1 - The two Chip Select pins
  • PMWR and PMRD - The Write and Read strobe pins

Remember: The PMP runs off of Peripheral Bus Clock 2 (PBCLK2), so it will run at whatever speed that clock runs at

Here's how we initialise up the PMP:

    asm volatile("di");     // Disable all interrupts             

    PMCONCLR = 0x8000;      // Turn PMP peripheral off before setting it up
    PMAEN = 0;              // Disable all address bits
    PMAENSET = 1;           // Enable Address Bit 0 (PMA0)

    PMCONbits.WRSP = 0;     // Set Write Strobe polarity to active low
    PMCONbits.RDSP = 0;     // Ret Write Strobe polarity to active low
    PMCONbits.PTRDEN = 1;   // Enable Read Strobe (RS)
    PMCONbits.PTWREN = 1;   // Enable Write Strobe (WR)
    PMCONbits.ADRMUX = 0;   // No MUXing please, all 16-bits on PMD<0:15>
    PMCONbits.SIDL = 0;     // Continue operation in idle mode
    PMCONbits.CSF = 00;     // PMCS2 enabled, PMCS1 is PMA14
    PMCONbits.ALP = 0;      // Set Address Latch Polarity bit active low
    PMCONbits.DUALBUF = 0;  // Disable Dual Buffer mode (use PMDIN and PMADDR registers)

    PMMODEbits.IRQM = 1;    // IRQ at the end of the Read/Write cycle
    PMMODEbits.MODE16 = 1;  // 16-bit mode
    PMMODEbits.MODE = 0b10; // Master mode 2
    PMMODEbits.WAITB = 3;   // Maximum wait / slowest speed
    PMMODEbits.WAITM = 15;  // Maximum wait / slowest speed
    PMMODEbits.WAITE = 3;   // Maximum wait / slowest speed
    PMMODEbits.INCM = 00;   // Do not automatically increment address when writing

    PMCONSET = 0x8000;      // Turn PMP peripheral on
    asm volatile("ei");     // Enable all interrupts again

I always initially set the PMP to the slowest speed before I initialise LCDs to counter some issues I've experienced. Once I've initialised the LCD I find I can set the PMP back to maximum speed with no issues.

<TL;DR>
Master mode 2? MUXing?

The PMP has two master modes, cleverly named mode 1 and mode 2. The main difference is that in mode 1, the RD/WR strobes are combined into a single signal and there's another signal called PMENB that determines whether there's a read or a write happening. In master mode 2, they are on two separate pins (namely PMRD and PMWR) and PMENB isn't used. My LCD has two separate pins for read and write strobes so I need to use master mode 2.

Multiplexing is a fancy way of either partially or fully using the same pins for address and data. This can save space but in turn takes longer to process because you have to handle some of the signals. For my LCD I don't need address bit (I just use PMA0 to control the data/command pin of my LCD) so I definitely don't want to use multiplexing.
</TL;DR>

Once you've set it up, it's incredibly simple to use the PMP:

  • Wait for the PMP to be available
while(PMMODEbits.BUSY);
  • Write data to the PMP
PMDIN = data;

Bam, done. And now, we move onto the fun of using this with LCDs.

Using the PMP to drive an LCD

Before starting, when you want to use an LCD with the PIC32MZ you need to know various things:

  • What controller chip does your LCD use?
  • Is it running in 8bit or 16bit mode?
  • Does it work with 3.3V signals?
  • What strobe (clock) signals does it need?

In my example, I have the following LCD:

  • The controller chip is an SSD1963
  • 16-bit mode
  • Yes, it can run fine off of 3.3V signals and use 5V as a backlight supply
  • It needs separate Read and Write strobes

All LCDs have their own special initialisation code that they need to be fed before they initialise. You can usually find this code on the Internet by googling, for example, "ssd1963 init". Another good resource is the excellent UTFT library source code. In my example, you can see the code for the SSD1963.

IMPORTANT: Please remember that your LCD may have a Chip Select (CS) line that usually needs to be set low for it to work at all. It may not work at all if you forget this!

Anyway, once your LCD initialises, it will usually look something like this (again, hopefully without that incredibly ugly, peeling off, bubbly plastic screen protector):

PIC32MZ - An initialised LCD with an ugly old screen protector

If you've gotten this far, well done. You're actually almost done! Most LCDs have some way of setting the area you want to draw to. With my LCD, I have to do this every time I want to draw something, it doesn't wrap around. Look in the controller chip's manual for something about setting window or setting area.

OK, let's display an image. I'm using one of Tux from Linux, which I hope is a free image. If not, please contact me and I'll draw something in mspaint. I hope not though because my mspaint skillz suck. I got the image from Wikipedia. Before we can use this image, we need to convert it into a 16bit image in RGB565 format. We can do this at the same website we get UTFT from. Choose to output a .c file and it'll turn your image into a 16-bit array.

<TL;DR>
RGB565 is a way of representing Red, Green and Blue (RGB) data in 16 bits, using 5 bits for Red, 6 bits for Green and 5 bits for Blue. It's very different to the 24-bit or higher colour displays we use on our computers and the colour numbers are not interchangable.
</TL;DR>

In my example, I've stored the data in an array called Tux. I draw the image on the screen like this:

    LCD_set_address(0,0,210-1,248-1);   // Tux image is 210 x 248 pixels, but we start at 0 so subtract 1 from each dimension
    LCD_write_command(0x2C);            // For the SSD1963 command 0x2C enables writing of pixel data
    PMADDR = 1;                         // PMADDR = 1 means writing data, not command
    for (cnt = 0; cnt < 210 * 248; cnt++)
    {
        PMP_wait();                     // Wait for the PMP to be free
        PMDIN = Tux[cnt];               // Send across 16bits of data
    }

One more thing: Many of the LCDs I've messed with only seem to get re-initialised when you power them on and off again, so you may or may not see the grainy test pattern if you simply do a soft reset. YMMV. Using this method I've managed to store rather big 800x480 16-bit images in the PIC23MZ's internal flash (in an array) and write them across to the LCD without any visible flicker or delay, which is much better than some of the awful examples you can find on YouTube. Have fun and good luck!

Here's the code

Tags: code, pmp, lcd

Update and posts to come

An update on the site

Update 2018/11/09: Finally managed to finish soldering the board. Found a minor mistake that I'll fix in the next revision but for now, time to write a post or two. Here's the board, by the way:

PIC32MZ - Cramming as much as possible onto a 100mm x 100mm PCB

I've been waiting for almost 4 weeks for the latest iteration of my development board to get made and I'll hopefully have it soon. It is my attempt to combine a battery charger, LCD port, audio DAC, 8MB SPI SRAM, SD card, USB connector etc on the same board to make writing these posts easier. Hopefully I'll have that all within a week and can resume writing these massively popular posts. Who knows, maybe they'll even be of use to someone at some point!

What I currently have lined up in terms of posts is, in no particular order:
- Using interrupts on the PIC32MZ
- Using the Parallel Master Port (PMP) to control a graphical LCD
- Using Pulse Width Modulation (PWM) to play audio files
- Using I2S to play audio via an external DAC chip
- Using I2C to communicate with external peripherals
- Combining a lot of the above stuff to write an MP3 player using the Helix MP3 Decoder library
- Creating a USB device
And then, if there's time:
- Designing your own PIC32MZ dev board and getting it made cheaply online

There's a lot more stuff I want to get out there because I really do think the PIC32MZ is great and it's a shame that there's no community around it. Anyway, hopefully I'll post more in a week or so. See you then.

Tags: blah

FATFS on the PIC32MZ

How to use FATFS with the PIC32MZ

Update 29/10/2018: At the request of user Malte I'm uploading a sample project, using the older mmcpic32.c file. You need to modify both mmcpic32.c and also the PPS setup in main.c to make it work for your project. Please note this isn't by any means for fast transfers, it's just to get the SD card connection working :)
Here's my project

Update 26/10/2018: Endless problems with this stuff! :) Here's the old mmcpic32.c file. Remove mmcpic32.c and replace it with this oldmmcpic32.c:
Here's my oldmmcpic32.c

Update 16/10/2018: It seems my diskio.h is a bit different so I'm adding it here:
Here's my diskio.h

Update 06/09/2018: I haven't implemented the writing part yet so there may still be some compatibility issues with the latest FatFs. You may need to make the following changes to ffconf.h:

#define FF_FS_NORTC     1
#define FF_NORTC_MON    9
#define FF_NORTC_MDAY   6
#define FF_NORTC_YEAR   2018
#define FF_FS_READONLY  0
#define FF_FS_RPATH     1

This is part 2 of 2 of the article on SPI on the PIC32MZ. Last time I discussed how to set up SPI and all the tricks and traps involved therein. Today I will be discussing how to use the FATFS library with our code in order to give us access to the files on the SD card. I'm only covering reading of the files today, maybe at some point in the future I'll cover writing.

<TL;DR>

A bit of background and an explanation of what I'll cover today

The problem I set out to solve was that I needed to read 320x240 30fps 16-bit video data from an SD card and show it on an LCD. This is a huge amount of data and the built-in Harmony SD library is far too slow for this. I needed about 4.5MB/s and at the time it was giving me reads of 2MB/s. I came across some code by Riccardo Leonardi and Aiden Morrison (who can be found on the Microchip forums). They'd ported the FatFs interface to PIC32 for the old MX series in a file titled mmcpic32.c. The problem with this was it was also far too slow (it's 7 years old so I can understand that!). After examination of the file I realised I could use a combination of Enhanced Buffers mode and 32-bit SPI reads to speed up the reading section.

I bought a good SD card (Samsung Evo 32GB) and formatted it at 32kB/cluster. After implementing these changes I can get read rates of 4.6MB/s when using 32kB read buffers. Today I'll be discussing these changes. Please bear in mind that SD cards vary wildly in terms of speeds and your results will vary.

</TL;DR>

Before we get down to business, credit where credit is due:
- Most of the work was done by Riccardo Leonardi and Aiden Morrison from the Microchip forums, so thanks to them for their mmcpic32.c file.
- My friend Bryn Thomas did a lot of work in finding out the root cause of why SD transfers were so slow and in using 32-bit SPI and Enchanced Buffers modes
- The FatFs library by ChaN is what made this all possible, so huge thanks to ChaN and all their hard work. The project is being updated to this day (last update was September 3 2018, yesterday at the time of writing).

STEP 1

OK, before we get started you will need to download the FATFS library and extract the contents to your project folder. I'm using version R0.13b for reference.

STEP 2

The next step is to include the header files needed:

#include "ff.h"
#include "diskio.h"

STEP 3

Next, download the mmcpic32.c file and copy it into your project folder.

STEP 4

Open mmcpic32.c and modify the following to make it work with your project:

/* Port Controls  (Platform dependent) */
#define CS_SETOUT() TRISBbits.TRISB4 = 0 
#define CS_LOW()  LATBbits.LATB4 = 0    //MMC CS = L
#define CS_HIGH() LATBbits.LATB4 = 1    //MMC CS = H
//Change the SPI port number as needed on the following 4 lines
#define SPIBRG  SPI2BRG
#define SPIBUF  SPI2BUF
#define SPISTATbits SPI2STATbits
#define SPICONbits SPI2CONbits

For my example, I'm using SPI2 and I have CS on pin RB4.

STEP 5

In your main C file, you need to add the following code:

// Global variables
FIL file; // File handle for the file we open
DIR dir; // Directory information for the current directory
FATFS fso; // File System Object for the file system we are reading from
FILINFO fileInfo; // Information for the file we have opened (not really necessary to have this)

void init_disk()
{
    // Wait for the disk to initialise
    while(disk_initialize(0));
    // Mount the disk
    f_mount(&fso, "", 0);
    // Change dir to the root directory
    f_chdir("/");
    // Open the directory
    f_opendir(&dir, ".");
}

STEP 6

Finally, in main() or setup() or whatever you use, call:

init_disk();

And... that's that! Now using files can be done just like you would on any other platform. For example, to open a file called "test.txt" in the root directory, you'd do this:

f_open(&file, "/test.txt", FA_READ);

Now you have the file handle stored in file and can use it as you please. For example, to read 32768 bytes into an array, do this:

unsigned char buffer[32768]; // buffer stores the data read from the file
int bytes_read; // bytes_read stores how many bytes were actually read

f_read(&file, buffer, 32768, &bytes_read);

If count has a different number of bytes to 32768 that means the end of the file was reached before it could read 32768 bytes or an error occurred.

Here's the code

Tags: code

SPI on the PIC32MZ

What is SPI and how do we use it on the PIC32MZ?

Last time I covered Peripheral Pin Select, and this is something that will be needed in today's post about using SPI so if you don't know about it I suggest you read the previous post first.

SPI or Serial Peripheral Interface is a way of communicating with an external device using 3 or 4 wires, depending on whether we way one-way or two-way communication. A simple look in the infamous PIC32 datasheet under SPI reveals yet another extra datasheet we need to download, in this case entitled DS60001106. So fine, we download that and look at what pins SPI uses and we eventually get to this:

PIC32MZ - SPI pins

With SPI, there is a Master device and a Slave device. The master device is the one that controls the transaction and needs to send a clock signal to the slave device. If you come from Arduino the naming is a little different:
- SDI here means data into the PIC32 from some other device and in my example would also be known as MISO (Master In, Slave Out)
- SDO means data out of the PIC32 and to another device and in my example would also be known as MOSI (Master Out, Slave In)
- SCK is the clock signal and the name is pretty common across all platforms.
- SS is slave select, also known as CS (Chip Select).

An important thing to remember about SPI is that the master is the only one that can generate the clock signal. This means that if you want to get device from an SPI module or SD card or whatever, you have to send it data in order for it to get the clock signal it needs to send you some data back. The sending and receiving of data is done at the same time becaue there is a dedicated wire for each (namely, SDI and SDO). For example, let's look at the following scenario: I have an 8-bit SPI connected LCD, and I want to read pixel 1 from its display memory. The command to do this is 0xFE followed by the pixel number, which is 0x0001 (a 16-bit number). I'd send the following data:

  1. Send 0xFE, ignore reply.
  2. Send 0x00, ignore reply.
  3. Send 0x01, ignore reply.

Now the slave device has the command and the parameter, it needs time to process the data before it can respond. Some devices can do this instantly, but some need a few clock cycles in order to process it because they depend entirely on this clock signal to operate at all. In my example, the LCD needs 3 clock cycles before it can reply with a 16-bit number. So to give it three cycles, I need to send it dummy data. I've chosen to send 0xFF and it may be different depending on the peripheral but everything I've seen was fine with 0xFF. So now we have to:

  1. Send 0xFF, ignore reply.
  2. Send 0xFF, ignore reply.
  3. Send 0xFF, ignore reply.

OK, now the LCD is ready to send me a 16-bit number back. I'm in 8-bit mode so this means 2 clock cycles.

  1. Send 0xFF, store reply as high 8-bits.
  2. Send 0xFF, store reply as low 8-bits
  3. Combine words: result = (high << 8) | low;

So a simple transaction can actually take a long time. This is how all SPI transactions work, and needs to be kept in mind.

One of the cool things about SPI is we can attach multiple devices to the SDI, SDO and SCK wires at the same time. With this system, each attached device would need a dedicated Chip Select pin of its own. For example, RA0 could be chip select for an SD memory card, RA1 could be chip select for an SPI LCD, etc. Because of this, in most of my SPI projects where the PIC32 is acting in the role of master I do not use the SPI peripheral's SS pin I just assign it to whichever port is convenient to me. Note: In I2S (Inter-IC Sound) we usually have to use the SS pin as it generates a special clock signal. That's beyond the scope of today's blog post however.

This is going to be a two-part post and the eventual goal is to attach an SD card to my PIC32MZ and read the first 128 bytes from a file named "test.txt" in the root directory. We will be making use of the excellent FatFs library - available here - to access and read the file so the SD card must be FAT or FAT32 formatted. This is actually quite an involved process so today I'm just going to look at setting up and using the SPI peripheral.

OK phew, explanation of what we want to do out of the way, let's move on to choosing which pins we want to use the SPI peripheral on. After all, according to the datasheet there are "Six 4-wire SPI modules (up to 50 MHz)" so we're spoiled for choice. And if you believe that, you haven't been burned by PIC32MZ badly enough yet (or haven't been shopping). Way, waaaaaay down in the datasheet you'll see why it says "up to 50Mhz."

PIC32MZ - SPI speed trick

Waaah Waah Waah Waaaaaaaaah. Oh, and let's not forget that little note number 5!

"5: To achieve maximum data rate, VDD must be ≥ 3.3V, the SMP bit (SPIxCON<9>) must be equal to ‘1’, and the operating temperature must be within the range of -40°C to +105°C."

And let me guess, additionally, it must be the night of the second full moon of the month and between 9PM and 11PM on a Tuesday, right? Naturally, we want to access the SD card at 50Mhz and not 25Mhz. Nothing is ever easy and straightforward with the PIC32MZ. Anyway, in my example I'm going to be using SPI2, with SDI on RB3, SDO on RB5 and CS on RB4. The SCK2 pin is not movable via Peripheral Pin Select, and on the 144-pin chip I use it is always on pin 14 (RG6). OK, let's set up PPS for SDO2 and SDI2:

SDI2R = 0b1000; // RB3 = SDI2
RPB5R = 0b0110; // RB5 = SDO2

TRISBbits.TRISB3 = 1; // Set RB3 as input because it's SDI2

PPS out the way, let's look at actually setting up SPI2 peripheral. On the PIC32MX with PLIB we used to do this:

    SpiChnOpen(SPI_CHANNEL2,SPI_OPEN_MSTEN | SPI_OPEN_CKP_HIGH | SPI_OPEN_SMP_END | SPI_OPEN_MODE8, 64); 

If you want to do this on the PIC32MZ, there is no PLIB so you can use the following code to do the same thing, with one small difference:

void SPI_init()
{
    SPI2CONbits.ON = 0; // Turn off SPI2 before configuring
    SPI2CONbits.MSTEN = 1; // Enable Master mode
    SPI2CONbits.CKP = 1; // Clock signal is active low, idle state is high
    SPI2CONbits.CKE = 0; // Data is shifted out/in on transition from idle (high) state to active (low) state
    SPI2CONbits.SMP = 1; // Input data is sampled at the end of the clock signal
    SPI2CONbits.MODE16 = 0; // Do not use 16-bit mode
    SPI2CONbits.MODE32 = 0; // Do not use 32-bit mode (combines with the above line to activate 8-bit mode)
    SPI2BRG = 0; // Set Baud Rate Generator to 0 [discussed below]
    SPI2CONbits.ENHBUF = 0; // Disables Enhanced Buffer mode [also discussed below]
    SPI2CONbits.ON = 1; // Configuration is done, turn on SPI2 peripheral
}

This will setup the SPI2 port as a master in 8-bit mode at 50Mhz using the standard polarities devices expect. For more explanation, we turn to...

<TL;DR>

The SPI2BRG register controls the speed of the SPI peripheral, and it is controlled by the following formula from the manual: PIC32MZ - SPI BRG formula

A while ago I discussed how different peripheral bus clocks control different peripherals. In this case, Peripheral Bus Clock 2 controls both the UART and the SPI. We set the peripheral bus clock 2 to 100Mhz during the initialisation. The smallest we can set SPI2BRG to is 0, which means the fastest speed we can get is (100/2*(0+1)), which is 100/2 which is 50Mhz. Every time the clock signal changes from high to low a bit is transmitted/received, so let's look at the actual byte transfer rates we can expect. We have a maximum of 50Mbps in 8-bit mode, which is 50mbps / 8 = 6.25MB/s at an absolute maximum, though in reality we won't be getting near this rate and I've found we usually get somewhere around 4MB/s reliably with my file transfers.

The Enhanced Buffer mode enables a 16-byte deep FIFO (First In First Out) buffer, which enables us to load up 16 bytes of data before the sending even begins. In practice this saves a huge amount of time and gives us faster transfer rates because the SPI peripheral has less time when it is doing nothing and just waiting for us to give it data to send.

</TL;DR>

OK, SETUP IS DONE! Now how do we use it to send data? Thankfully, its dead simple to use. If we want to send a character stored in the data variable to SPI2 we can do it like this:

char SPI_send(char data)
{
    SPI2BUF = data; // Send **data** to SPI2BUF.
    while (SPI2STATbits.SPIRBE); // Which the receive buffer is empty, loop
    return (char)SPI2BUF; // Return whatever is in SPI2BUF
}

<TL;DR>

A note on Enhanced Buffer Mode: We don't use SPI2STATbits.SPIRBE when in enhanced buffer mode, instead we check the value of SPI2STATbits.RXBUFELM, which tells us how many elements (bytes) are stored in the receive buffer. If I send 16 bytes like this:

int cnt;
for (cnt = 0; cnt < 16; cnt++)
{
    SPI2BUF = 0xFF;
}

The SPI2 peripheral will start sending data. At the very beginning, the value of SPI2STATbits.TXBUFELM will be 16 because it has 16 bytes queued up, and the value of SPI2STATbits.RXBUFELM will be 0 because it hasn't received anything yet. As it process the 16 bytes automatically, eventually SPI2STATbits.TXBUFELM will move down to 0. As it moves down to 0, SPI2STATbits.RXBUFELM will move up to 16 as it receives the replies. I can then read the data as follows:

int cnt;
char data[16];
for (cnt = 0; cnt < 16; cnt++)
{
    data[cnt] = SPI2BUF;
}

As soon as I read from SPI2BUF, the peripheral automatically removes the top entry from the buffer, puts the next value in SPI2BUF and reduces RXBUFELM by 1.

</TL;DR>

A very important note about the timing of sending and receiving data

When we send data via SPI, we always get some data back immediately but it is vital that you remember this reply data is actually the reply to the previously sent data byte. For most transactions, this doesn't matter. We often just want to send tons of data and the replies from the slave device are not important. However, when we are working with something where the replies are important, this can be a potentially dangerous trap. As such, there are a lot of "dummy" bytes sent in SPI transactions in order to give the slave the clock signals it needs to reply, and the dummy bytes are often 0xFF.

Wow, that was way more complicated that I intended it to be. Next time we'll use this code and understanding of SPI to communicate with an SD card.

Here's the code

Tags: code