Pulse Width Modulation (PWM) and servos on the PIC32MZ

First, an update on why my timing has been off by a factor of two until now (doh!). It all comes down to this line:

   PB7DIVbits.PBDIV = 1; // Peripheral Bus 7 Clock Divisor Control (PBCLK7 is SYSCLK divided by 1)

It was supposed to be this:

   PB7DIVbits.PBDIV = 0; // Peripheral Bus 7 Clock Divisor Control (PBCLK7 is SYSCLK divided by 1)

I've gone and updated all the old source code, sorry for the dumb mixup :)

Additionally, I've finally started moving my source code to Github. If my site ever goes down, you can find the code here.

Using Pulse Width Modulation (PWM) on the PIC32MZ

Pulse Width Modulation (PWM) overview

Please note: In this post I'm just going to be talking about using PWM in order to send pulses of varying period, not in the context of creating a variable voltage output. So yes, there's a lot more to it than I'm going to cover today. On with the post!

Using the timer code from the last post (on timers), we can create an interrupt handler to give us a pulse like this:

PIC32MZ - 1kHz timer scope verification

That all happens in the background thanks to interrupts. However, the PIC32 series comes with a much better way to generate pulses, and it's all handled by the PIC32 in hardware. The module is called the Output Compare (OC) module, and it works as follows:

  • It's attached to a specific timer
  • When a timer ticks occurs, the value of the timer's count register (TMRxCNT) is compared to the value in the Output Compare Register (OCRx), and if TMRxCNT is bigger, one of several user-defined things can happen.

For today, we're looking at the PWM mode of the Output Compare module. What this does, is when the value TMRxCNT is lower than the value in OCRx, it sets the output OCx high. When TMRxCNT eventually gets bigger than OCRx, it sets the output OCx low. So how does this help us? Let's take a look at an example:

Say I want to generate a pulse that has a frequency of 1Hz (i.e. once every second) and I want to set port pin RC1 high for the first half and low for the second half of the pulse. Using an interrupt handler, I could do the following:

volatile int counter;

void init_timer2()
{
    T2CON   = 0x0;      // Disable timer 2 when setting it up
    TMR2    = 0;        // Set timer 2 counter to 0
    IEC0bits.T2IE = 1;  // Disable Timer 2 Interrupt

    // Set up the period. Period = PBCLK3 frequency, which is SYS_FREQ / 2, divided by 1000Hz and then divided by 8 for our chosen pre-scaler.
    PR2 = SYS_FREQ / 2 / 1000 / 8;

    // Set up the pre-scaler
    T2CONbits.TCKPS = 0b011; // Pre-scale of 8

    IFS0bits.T2IF = 0;  // Clear interrupt flag for timer 2
    IPC2bits.T2IP = 3;  // Interrupt priority 3
    IPC2bits.T2IS = 1;  // Sub-priority 1
    IEC0bits.T2IE = 1;  // Enable Timer 2 Interrupt

    // Turn on timer 2
    T2CONbits.TON   = 1;
    counter = 0;
}

void __attribute__((vector(_TIMER_2_VECTOR), interrupt(ipl3soft), nomips16)) timer2_handler()
{
    IFS0bits.T2IF = 0;  // Clear interrupt flag for timer 2
    counter++;
    if (counter < 500)
        LATCSET = 1;
    else
        LATCSET = 0;

    if (counter >= 1000) counter = 0;
}

And yes, that would work fine. However, it is unnecessarily complicated. With PWM it's much less code intensive because it's all handled in hardware. Let's take a look at that same code but using the Output Compare module in PWM mode:

void init_timer2()
{
    T2CON   = 0x0;      // Disable timer 2 when setting it up
    TMR2    = 0;        // Set timer 2 counter to 0

    /* Set up the period. Period = PBCLK3 frequency, which is SYS_FREQ / 2, divided by 1000Hz and then divided by 8 for our chosen pre-scaler. */
    PR2 = SYS_FREQ / 2 / 1000 / 8;

    // Set up the pre-scaler
    T2CONbits.TCKPS = 0b011; // Pre-scale of 8

    // Turn on timer 2
    T2CONbits.TON   = 1;

    OC6CON = 0;             // Turn off Output Compare module 6
    OC6CONbits.OCTSEL = 0;  // Interrupt source for this module is Timer 2
    OC6CONbits.OCM = 0b110; // Output Compare Mode (OCM) is 6, which is PWM mode
    OC6RS = SYS_FREQ / 2 / 1000 / 8 / 2;    // We want half the length of PR2
    OC6CONbits.ON = 1;      // Turn on Output Compare module 6 (OC6)
}

And that's it. Notice what's missing?We don't need to make an interrupt handler that wastes processing time and we don't even need to set up the interrupt for Timer 2. The PIC32's Output Compare module will handle it all for us! Let's take a look at the code:

   OC6CONbits.OCTSEL = 0;   // Interrupt source for this module is Timer 2

Output Compare Timer Select (OCTSEL) selects which timer to use as the source of interrupts. It can have either a 0 or 1 value, which will set it to Timerx or Timery. What does that mean? Let's look in the datasheet:

PIC32MZ - Output Compare Timer Select

OK, as you can see, certain OC modules work with certain timers. Do not just assume they all work with Timer 2 or 3 :) In my example, OC6 works with Timer 2 (x) or Timer 3 (y) and I've set up Timer 2 already, so I set OCTSEL to 0.

    OC6CONbits.OCM = 0b110; // Output Compare Mode (OCM) is 6, which is PWM mode

Where does that come from? Again, the datasheet (beginning to see a pattern here?).

PIC32MZ - Output Compare Mode Select

<TL;DR>
What's the difference between modes 6 and 7? There are two extra PPS pins available to the Output Compare modules, called OCFA and OCFB (Output Compare Fault A and B). When a 0 is detected on these pins, the output of the Output Compare modules will be disabled. This can be useful in certain situations, but I've never actually had need to use this for any of my simple programs. Therefore, I use mode 6 which has the fault pin disabled and stay away from mode 7 which has it enabled.
</TL;DR>

Next, set the Output Compare Register (OCR) value:

    OC6RS = SYS_FREQ / 2 / 1000 / 8 / 2;    // We want half the length of PR2

This is very similar to setting up the Period Register of the timer (PR2 in my case). I've set it to half the value of PR2.

IMPORTANT: Although the Output Compare module compares the value in OC6R to PR2, when writing the value we must write to the OC6RS register. It will then move the value over to OC6R itself on the next cycle.

    OC6CONbits.ON = 1;      // Turn on Output Compare module 6 (OC6)

That's pretty self explanatory.

IMPORANT: The Output Compare modules are set up using Peripheral Pin Select, so if you don't set them up they won't work at all. For example, to set port C1 to Output Compare module 6 (OC6), you would do this:

    RPC1R = 0b1100; // Set RC1 to OC6

Remember, all of these PPS selections can be found in the PIC32MZ datasheet, currently in section TABLE 12-3: OUTPUT PIN SELECTION.

Using PWM to control an analog servo motor

I bought some PowerHD HD-1370A miniature servo motors from Banggood in order to make a very, very simple robot that can open and close its mouth. One of the things I always envied about Arduino users is that the code and libraries are all there for them. However, compared to the PIC32MZ the Arduino sucks in almost every way imaginable. Let's get this motor working with the PIC32MZ.

Servo motors usually require control signals to be sent in signals that are 20 milliseconds long. In general, the pulses themselves are between 1 and 2 milliseconds long, with 1 millisecond meaning turn as far clockwise as you can, 1.5 milliseconds meaning go to the mid-point (neutral) and 2 milliseconds meaning turn as far counter-clockwise as you can. The pulse is high for the duration and then for the rest of the 20 milliseconds it needs to go low again. This value of 1 to 2 milliseconds is very general and each motor has different requirements, so please always consult the motor's datasheet.

First, let's take a look at the datasheet for my motor:

PIC32MZ - PowerHD HD-1370A datasheet

The numbers I care about there are the degrees it can travel, the pulse width range of 800us to 2200us and the neutral position of 1500us. So, in theory, if I send a control pulse that is high for 800us it will rotate clockwise as much as it can and if I send a pulse that is high for 2200us it will rotate counter-clockwise as far as it can go.

OK, so what I need:

  • A timer that has a frequency of 50Hz (to give me 20ms / timer tick)
  • A way of sending out a pulse that shifts between high and low during the 20ms period
  • A way of setting the duty cycle (high/low time) of the control signal

Let's set up the timer first, using Timer 2. As always, let's check if this value fits in the Period Register (PR):

PR2 = SYS_FREQ / 2 / 1000 / 8 = 100,000,000 / 50 / 8 = 250,000

Nope. We need the value to be less than 65,536. A quick look at the possible pre-scaler values gives us 32, let's try plug that in:

PR2 = 100,000,000 / 50 / 32 = 62500

This works fine, so we need to set TCKPS to 0b101, which will give us a pre-scaler value of 32.

void PWM_init()
{
    T2CON   = 0x0;      // Disable timer 2 when setting it up
    TMR2    = 0;        // Set timer 2 counter to 0

    /* Set up the period. Period = PBCLK3 frequency, which is SYS_FREQ / 2, divided by 50 Hhz and then divided again by 32 for our chosen pre-scaler. */
    PR2 = SYS_FREQ / 2 / 50 / 32;

    // Set up the pre-scaler
    T2CONbits.TCKPS = 0b101; // Pre-scale of 32

    // Turn on timer 2
    T2CONbits.TON   = 1;

    OC6CON = 0;             // Turn off Output Compare module 6
    OC6CONbits.OCTSEL = 0;  // Interrupt source for this module is Timer 2
    OC6CONbits.OCM = 0b110; // Output Compare Mode (OCM) is 6, which is PWM mode
    OC6RS = 0;              // Keep the signal low for the entire duration
    OC6CONbits.ON = 1;      // Turn on Output Compare module 6 (OC6)
}

Now, let's calculate how to send a 1000us long high pulse. The Period Register value of 62500 represents the full 20ms long signal. Let's use that to work out the value we need to put into OC6RS:

(1 / 20) * 62500 = 0.05 * 62500 = 3125

There are ways to have it slightly more accurate in cases where the number is fractional, but for now let's try that out and see what happens:

/* in main() */
OC6RS = 3125;               // Move servo to 1ms position

Let's see what we get on the scope:

PIC32MZ - 20ms PWM signal

OK, excellent. The period of the signal is 20ms, let's zoom in on that pulse though and see if it's 1ms:

PIC32MZ - 1ms PWM pulse

Hooray, we have what we want. So plugging my servo motor's signal pin into this wire (along with giving it Vcc and Ground of course) will result in the servo moving to the position I want it to. You can even get it to move between positions easily now:

/* in main() */
while (1)
{
    OC6RS = 3125;       // Move servo to 1ms position
    delay_ms(1000);
    OC6RS = 6250;       // Move servo to 2ms position
    delay_ms(1000);
}

Remember, all servos are different and require different timing. Especially if you're like me and source them from AliExpress, Banggood and eBay. The motors found there are often clones and have slightly different timing to the originals. Good luck!

Here's the code

Tags: code, PWM, motors

Timers and Interrupts on the PIC32MZ

Using timers on the PIC32MZ

Timers overview

Timers are one of the things that the PIC32MZ does really well. It has 9 independent 16-bit timers, some of which can be combined to make 32-bit timers. It also has an awesome Output Compare module, which I'll cover next time with Pulse Width Modulation (PWM). All the timers work as follows:

  • On every PBCLK3 clock cycle, the value of the timer counter (TMR) is increased.
  • This value is compared to the Period Register (PR) value.
  • If the value is bigger or equal to PR, an interrupt is generated and the value of the counter is reset.

From this it can be seen that the most important registers for us are the Timer Register (TMR) and Period Register (PR).

IMPORANT: The timers on the PIC32MZ are all controlled by Peripheral Bus Clock 3 (PBCLK3), so whatever you set that to will directly affect all the timers.

<TL;DR>
16-bit? 32-bit? As you likely know, 16-bit unsigned variables can store a maximum value of 2^16 - 1, or 65535, and 32-bit unsigned variables can store 2^32 -1, or 4,294,967,295 (~4 billion). This can become very important when we are either wanting super precision or extremely low frequency timers, as I will show later.
For 32-bit timers, timers 2 and 3, timers 4 and 5, timers 6 and 7 and timers 8 and 9 can be combined to form the 32-bit timers.
</TL;DR>

Calculating the value to put into the Period Register (PR)

Say for example we want to generate a timer that runs at 10kHz. We calculate the period like this:
Period Register = Peripheral Bus Clock 3 Frequency / 10000.
In my examples, PBCLK3 runs at 100MHz, so this gives us: PR = 100,000,000 / 10,000 = 10,000.
10000 is much smaller than the maximum value of 65,535, so no problems there.
Now let's try that again but at 1kHz:
PR = 100,000,000 / 1,000 = 100,000.
Uh oh. 100,000 cannot fit into 65,535 so we're going to have the timer running at a frequency we don't expect. What can we do about this? Well, two things:

  • Change over to a 32-bit timer, which is overkill of the highest degree, OR
  • Use the timer's built-in pre-scaler

Using the pre-scaler

OK before I start this section, be careful which timer number you use. Timer 1 has some differences to Timers 2 to 9 and as such I recommend using Timers 2 to 9 unless you know what you're doing. In my example today I'll be using Timer 2. Anyway, the timer has a kind of built-in divider circuit called a pre-scaler. It works like this:

  • On every PBCLK3 clock cycle, the value of an internal counter is increased.
  • This value is compared to pre-scaler setting (TCKPS) in the Timer 2 Control Register (T2CON).
  • If the value is bigger or equal to TCKPS, then the internal counter TMR is incremented.

Examination of the TxCON register in the datasheet shows this:
PIC32MZ - Pre-scaler settings

So, bringing this back to my example of a 1kHz timer, I can now accomplish it as follows:

  • Set the pre-scaler to 8 (TCKPS = 011)
  • Calculate the period as follows: PR = 100,000,000 / 1,000 / 8 = 12500.

12500 fits neatly into a 16-bit register, so no problems there. Do be aware that some pre-scaler values will lead to fractional numbers, which will just be truncated and lead to inaccurate timing. For example, a pre-scaler value of 64 would lead to a required PR of 1562.5.

Setting up a timer

OK, phew. Theory out the way, let's see how to do this in code:

void init_timer2(int period)
{
    T2CON   = 0x0;      // Disable timer 2 when setting it up
    TMR2    = 0;        // Set timer 2 counter to 0

    /* Set up the period. Period = PBCLK3 frequency, which is SYS_FREQ / 2, divided by the frequency we want and then divided by 8 for our chosen pre-scaler. */
    PR2 = SYS_FREQ / 2 / period / 8;

    // Set up the pre-scaler
    T2CONbits.TCKPS = 0b011; // Pre-scale of 8

    // Turn on timer 2
    T2CONbits.TON   = 1;
}

Bam, that's it! It's incredibly simple to do. Of course, now the timer is running and that's great and all but we want to use the timer for something. For that, we need to discuss interrupts.

Interrupts on the PIC32MZ

The PIC32MZ is a fast processor but it is still a single core, single threaded processor. This is important because if we are running a while() loop and our timer keeps interrupting our code every 1/1000 times a second, the PIC32MZ will leave what we are doing in the while() loop, jump to the code for doing whatever we told it to do when the timer ticks, complete that, and then jump back to the while loop until it is interrupted again. These interruptions are called interrupts, funnily enough.

The PIC32MZ has a very deep interrupt system. In short, each interrupt can be assigned a priority from 1 to 7 and a further sub-priority of 0 to 3.
A higher interrupt priority gets handled first and a lower interrupt priority gets handled later. Further, a higher priority interrupt can actually interrupt a lower priority interrupt but the reverse is not true. Sub-priorities can be used when interrupts have the same pririoty, as a further way of sub-dividing which of these interrupts is more important.

First, before we begin, you should generally enable Multi Vectored interrupt mode. This will save you many headaches when working with interrupts and you're finding they don't work and you can't work out why. It basically tells the PIC32 to allow use of a different handler for each different type of interrupt (Timer 2, Timer 3, etc). Thankfully, this is very easy to do:

    INTCONbits.MVEC = 1;

The timer 2 interrupts have four important bits associated with them:

  • Timer 2 Interrupt Enable (T2IE)
  • Timer 2 Interrupt Flag (T2IF)
  • Timer 2 Interrupt Priority (T2IP)
  • Timer 2 Interrupt Sub-priority (T2IS)

Searching the infamous datasheet reveals that T2IE is in the IEC0 register, T2IF is in the IFS0 register and both T2IP and T2IS are in the IPC2 register. It is left as an exercise to the student to search the datasheet to find where the other timer interrupt bits are located :)

OK. let's re-visit that init_timer2() function from before, but this time let's enable the Timer 2 Interrupt with a priority of 3 and a sub-priority of 1.

void init_timer2(int frequency)
{
    T2CON   = 0x0;      // Disable timer 2 when setting it up
    TMR2    = 0;        // Set timer 2 counter to 0
    IEC0bits.T2IE = 1;  // Disable Timer 2 Interrupt

    // Set up the period. Period = PBCLK3 frequency, which is SYS_FREQ / 2, divided by the frequency we want and then divided by 8 for our chosen pre-scaler.
    PR2 = SYS_FREQ / 2 / frequency / 8;

    // Set up the pre-scaler
    T2CONbits.TCKPS = 0b011; // Pre-scale of 8

    IFS0bits.T2IF = 0;  // Clear interrupt flag for timer 2
    IPC2bits.T2IP = 3;  // Interrupt priority 3
    IPC2bits.T2IS = 1;  // Sub-priority 1
    IEC0bits.T2IE = 1;  // Enable Timer 2 Interrupt

    // Turn on timer 2
    T2CONbits.TON   = 1;
}

OK, that was a lot of explaining for very little code. There's one final step, writing the interrupt handler. Almost done, bear with me!

Writing an interrupt handler on the PIC32MZ

I want my timer interrupt handler to toggle pin RH0 every time it is called. Let's take a look at how I write this:

void __attribute__((vector(_TIMER_2_VECTOR), interrupt(ipl3soft), nomips16)) timer2_handler()
{
    IFS0bits.T2IF = 0;  // Clear interrupt flag for timer 2
    LATHINV = 1 << 0;   // Toggle pin RH0
}

OK, the code itself is very simple but hoo boy that declaration. Let's break it down:

  • __attribute__ tells the compiler we are going to give it specific instructions for how to handle this function.
  • The interrupt vector _TIMER_2_VECTOR is defined by the include file p32mz2048efh144.h. You can also use the IRQ number found in the datasheet directly, in this case 9.
  • The ipl3soft refers to Interrupt Priority Level 3 (yes, that's a lowercase L). If you want to know what the soft part means, refer to the <TL;DR> below.
  • nomips16 forces the compiler to generate code using the MIPS32 instruction set and not the MIPS16 instruction set. This is required for interrupts on the PIC32.
  • timer2_handler() is just what I named the function, you can name it whatever you want.

Inside the function itself, there are only two lines. The first line clears the interrupt flag for timer 2. If you do not clear this flag, the interrupt handler will immediately and repeatedly get called again until it is cleared. It is recommended that you clear this flag immediately, and that your code inside an interrupt handler does not take too long. Keep interrupt handler code short and simple, don't write files or anything inside an interrupt because by the time the file writing is done the PIC32MZ will have missed the timing of several interrupts and things will go pear-shaped. And that's it, interrupt handler done!

Let's take a look with an oscilloscope to see if this gives us what we want:

PIC32MZ - 1kHz timer scope verification

And there it is, just what we expect. The signal is high for 1ms, low for 1ms, high for 1ms, etc, which means it's changing at a rate of 1kHz. Unlike the FatFs disasters, it seems to have worked out this time! :)

<TL;DR>
There is a rather large problem with interrupts in general. The processor needs to temporarily pause what it was doing, jump to the interrupt handler and then go back to what it was doing. This means, of course, that it needs to first save what it was doing so it can go back to it later. In case of the PIC32MZ, this means it needs to save the contents of some important registers before it can actually call the interrupt handler. This delay is called interrupt latency. For almost all of the stuff I do, which is not time-critical, this delay doesn't make much difference but for time critical stuff it can be a huge problem. The PIC32MZ combats this by having a Shadow Register Set (SRS) that you can use to minimize this latency. Today's post is long enough, however, perhaps in the next post. Or you're welcome to Google it for yourself :)

To summarise:

  • Setting it to soft forces it to use software context switching, i.e. manually save all the registers. This is the slowest option.
  • Setting it to srs forces it to use the shadow register set, which is much faster. It will also crash your program if you haven't set it up.
  • Setting it to auto tells it to choose because software context switching and using the shadow regiter set. I've had issues with this when I hadn't set up the shadow register set so be warned!

</TL;DR>

Here's the code

Tags: code, timers, interrupts

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