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

UART and Peripheral Pin Select (PPS) on PIC32MZ

How to use the PPS functionality and how to set up the UART

It's been a while, so time to blog about something worthwhile! A lot of the cool peripherals (devices) on the PIC32MZ aren't assigned to specific pins, you can choose between a set of pins on which to place them. For today's example, I will be looking at how to set up the PPS pins to give me UART port 5 on pins RF0 and RF1. There are two pins that we want, the receive pin (RX) and the transmit pin (TX). So let's go look at the datasheet and see what it says. PIC32MZ - PPS Input pins!

These are some of the input pins we can set up. We are looking for U5RX, the RX pin for UART number 5. As you can see, it's on the left. The pins on the right are the possible pin numbers we can place that pin on. I want to place it on pin RF1, which is also called RPF1. Looking on the right, I can see that the code to do this is 0100. So now, we go to the code and set U5RX to 0b0100, like this:

U5RXR = 0b0100; // RF1 = U5RX

Simple, if you know how. But wait, there's more! Because this is now an input pin, we need to set up the TRISF register to make that pin an input too.

TRISFbits.TRISF1 = 1; // Make RF1 an input

OK, now let's set up the TX pin. Let's look at the datasheet again.

PIC32MZ - PPS Output pins!

For the output pins, the peripherals are listed on the right and the possible ports on the left. We can see on the right that U5TX has a code of 0011, and as we want it on pin RF0 we set it up as follows:

RPF0R = 0b0011; // RF0 = U5TX

OK, now we've assigned the UART 5 peripheral to pins RF0 and RF1, it's time to initialise it. In this example, I want to set it up at a baud rate of 38400bps so I can communicate with my FTDI cable connected at those pins. I like doing this for debug logs and just communicating with the PC in general. Unfortunately, when we look at the datasheet and the UART section we get to read this:

"This data sheet summarizes the features of the PIC32MZ EF family of devices. It is not intended to be a comprehensive reference source. To complement the information in this data sheet, refer to Section 21. “Universal Asynchronous Receiver Transmitter (UART)”(DS60001107) in the “PIC32 Family Reference Manual”, which is available from the Microchip web site (www.microchip.com/PIC32)."

Wonderful, so we google DS60001107 and find the UART datasheet and down the rabbit hole we go. Nothing is quick and easy to do on the PIC32. Let's look at the speed calculation formula:

PIC32MZ - PPS Output pins!

Very long story short, the UART peripherals are run off of Peripheral Bus Clock 2 (PBCLK2), which we set to 100Mhz in the previous blog post. There is a "standard speed" mode and a "high speed mode". The standard speed mode has a maximum speed of (PBCLK2 / 16) which is 6.25Mbps in our example. The high speed mode has a maximum speed or (PBCLK2 / 4), which is 25Mbps for us. As I'm using a much, much slower speed I'm going to be setting it up in standard mode. Here's the code to initialise UART 5 at 38400bps:

void UART_Init()
{
    int pbClk;

    pbClk = SYS_FREQ / 2; // Our PBCLK2 divider was set to 1, so PBCLK2 is exactly half the speed of the system clock, or 100Mhz

    U5MODE = 0; // Set UART 5 off prior to setting it up
    U5MODEbits.BRGH = 0; // We want standard speed mode. Not necessary as we just set U5MODE to 0 so this is just for explanation's sake
    U5BRG = pbClk / (16 * 38400) - 1;// This is the formula straight from the datasheet
    U5STA = 0; // Disable the TX and RX pins, clear all flags
    U5STAbits.UTXEN = 1; // Enable the TX pin
    U5STAbits.URXEN = 1; // Enable the RX pin
    U5MODEbits.PDSEL = 0; // PDSEL controls how many data bits and how many parity bits we want, this is the default of 8-bit data, no parity bits that most terminals use
    U5MODEbits.STSEL = 0; // STSEL controls how many stop bits we use, let's use the default of 1
    U5MODEbits.ON = 1; // Turn on the UART 5 peripheral
}

Phew. That's actually the hardest part done. Now we can immediately start writing data to UART 5 by writing to U5TXREG, like this:

char c;
c = 'A';
U5TXREG = c;

But there's a much cooler and easier way to do this that allows us to use printf. We need to overwrite the _mon_putc function, which is thankfully really easy:

void _mon_putc (char c)
 {
   while (U5STAbits.UTXBF); // Wait til current transmission is complete
   U5TXREG = c;
}

And that's it! Now we can use printf to send data directly to the serial port UART 5. Remember, when connecting the FTDI cable you need to connect the FTDI's TX pin to the PIC32's RX pin and the FTDI's RX pin to the PIC32's TX pin. I've attached a very basic, but working, program that outputs a counter to the serial port every second.

Here's the code Have fun!

Tags: code

Delays on PIC32MZ

How to make millisecond and microsecond delays

Ever wanted to make an LED toggle on and off every second? It's the dream of every hobbyist, especially when we're dealing with the PIC32MZ. A sign of success, that things are working and clock speeds are right. Unfortunately, the XC32 compiler doesn't give us a delay function to call. A quick google of "PIC32 millisecond timer" brings back countless results relating to other PIC devices, not PIC32, and people saying we should be using Harmony. There are also plenty of results relating to using the Timer peripherals and the calculations look complex and really aren't what we want for this simple task. Oh, and "read the data sheet" of course.

Instead, let's do it the easy way. There's a built-in timer in every PIC32 called the Core Timer. It doesn't need to be set up and it is always running. It runs at half the system frequency, so if you're running at 200Mhz, it'll update at 100Mhz. First, let's add a define for how fast we've set the fuses to.

#define SYS_FREQ 200000000 // Running at 200MHz

This value will be useful for many calculations we'll need to do later. OK, so now how do we use the Core Timer? Well, first we need to calculate how long we need to wait.

<TL;DR>

In our example, the core timer updates at 100Mhz. Let's say we want to wait for 500 microseconds. First, let's calculate how many clock ticks that is. One clock tick takes 1 / 100,000,000 seconds, which is 0.000001 seconds, which is 0.01 microseconds. So this means that for every 100 clock ticks 1 microsecond has passed. So, for 500 microseconds we need to wait 500 * 100 = 50,000 clock ticks.

</TL;DR>

Still with me? Good :) Let's put that knowledge to use and make a microsecond delay function:

void delay_us(unsigned int us)
{
    // Convert microseconds us into how many clock ticks it will take
    us *= SYS_FREQ / 1000000 / 2; // Core Timer updates every 2 ticks

    _CP0_SET_COUNT(0); // Set Core Timer count to 0

    while (us > _CP0_GET_COUNT()); // Wait until Core Timer count reaches the number we calculated earlier
}

First we calculate how many clock ticks we need to wait. Then we set the Core Timer counter to 0 by calling

CP0_SET_COUNT(0)

<TL;DR>

CP0_SET_COUNT(value) is a built-in way to set the Core Timer's value. For the fancy pantses among us, yes, you can use:

_mtc0(9, 0, 0); // Register 9 is the Count register, set it to 0

But it comes down to the same thing, so take your pick.

</TL;DR>

Then we just need to wait until the Core Timer counter reaches the value we set and the delay will be done. Simple! Let's use that to make a millisecond delay now.

void delay_ms(int ms)
{
    delay_us(ms * 1000);
}

Is that cheating? Maybe? Off by a few clock cycles? Probably. As the audience for this blog is going to be tiny (or non-existent) anyway I don't think it matters :) Oh, one more thing. The Core Timer is a 32-bit number, which means it can only count to 4 billion before it'll wrap back around to 0. So don't use delay_ms() for hundreds of seconds. At least up to 20 seconds should be fine. Now let's make that LED dance!

while (1)
{
    LATAINV = 1 << 0; // Toggle LAT A bit 0
    delay_ms(1000); // Delay 1 second
}

LATAINV is an easy way to toggle bits on Port A and requires no work on our part. Hope the TL;DR tags help :)

Here's the code

Tags: code

A PIC32MZ program skeleton

How to start every program (at 200Mhz)

Last time I discussed how to set the PIC32 and its peripherals at maximum clock speeds. This time I'll be going over some common "traps for young players", as Dave on EEVBlog would say. The PIC32 is great but starting out can be a nightmare.

TL;DR warning: To be honest, I just want to start writing about how to code this thing but the PIC32MZ has so many traps and pitfalls that I think need to be discussed first. Just download the code at the end :)

Let's take a simple example: I want to turn on an LED on RA0 (aka PORT A bit 0). So, coming from other microcontrollers, I know I need to set the pin to an output. So I make this incredibly simple program:

void main()
{
    set_performance_mode(); // Set everything to maximum speed

    TRISACLR = 1 << 0; // Clear bit 0 of TRISA. which makes PORT A bit 0 an output

    LATASET = 1 << 0; // Set bit 0 of PORTA to 1
}

I connect a 330 ohm resistor from RA5 to the LED, and the cathode of the LED to ground. Laughing to myself at how easy that was, I compile and run it and... why isn't the LED turning on? Did I connect it to the wrong pin? No, I checked that. Ah yes, now I remember. The PIC32 starts all ports out as analog and not digital so we have to set them up. We do that by setting

ANSELA = 0;

There are 32 bits in ANSELA, and each of them corresponds to a bit on PORT A. Setting a bit to 0 means we want to use it for digital I/O and setting it to a 1 means we want analog I/O. I usually just set the whole register to 0 and in those rare cases I need to use the ADC I'll set the appropriate bit. So let's add that in.

void main()
{
    set_performance_mode(); // Set everything to maximum speed

    ANSELA = 0; // Set all of PORT A's pins to digital

    TRISACLR = 1 << 0; // Clear bit 0 of TRISA. which makes PORT A bit 0 an output

    LATASET = 1 << 0; // Set bit 0 of PORT A to 1
}

Haha! That's it, it has to work this time! Compile, run and... what... the... heck? Maybe the oscillator wasn't configured correctly? Nope, went into that in excruciating detail last time. Maybe the pin is broken, because that's likely. Let's give PORT B a go:

void main()
{
    set_performance_mode(); // Set everything to maximum speed

    ANSELB = 0; // Set all of PORT B's pins to digital

    TRISBCLR = 1 << 0; // Clear bit 0 of TRISB. which makes PORT B bit 0 an output

    LATBSET = 1 << 0; // Set bit 0 of PORT B to 1
}

Compile, run and it works! But why? Does this mean I can't use RA0? Welcome to PIC32 Hell. You can google about and maybe get the answer, scanning forums full of people who know what's going on but enjoy taunting noobs. "Read the datasheet" is some awesome advice that's thrown around an awful lot. So let's do that.

PIC32MZ - Read the datasheet!

There are two arrows there, red and green. The red arrow is pointing to the bad RA0 that didn't work and green is pointing to the good RB0 that worked fine. If you look next to RA0 you'll see 3 little letters that can mean hours of frustration. TMS. There are more, TCK, TDI and TDO. So what gives?

As we've established by now, Microchip's idea of a default configuration is somewhat different from what a hobbyist wants. This means that every peripheral may be set to the wrong clock speed, everything is set to analog and interfaces like JTAG are enabled by default. What is JTAG? For most of us, it's something that takes away our ability to use the ports that we want :) To prevent many many hours of "reading the datasheet", please ensure you have these fuses set:

#pragma config FWDTEN = OFF             // Watchdog Timer Enable (WDT Disabled) - Enable for random resets
#pragma config FDMTEN = OFF             // Deadman Timer Enable (Deadman Timer is disabled) - See above
#pragma config JTAGEN = OFF             // JTAG Enable (JTAG Disabled) - Enable to lose four port outputs
#pragma config DEBUG = OFF              // Background Debugger Enable (Debugger is disabled) - Set if off and MPLAB will override it when you debug
#pragma config CP = OFF                 // Code Protect (Protection Disabled) - This will encrypt your PIC32 if enabled
#pragma config PMDL1WAY = OFF           // Peripheral Module Disable Configuration (Allow multiple reconfigurations) - Enable to only be allowed to make one change to peripheral pin select settings
#pragma config IOL1WAY = OFF            // Peripheral Pin Select Configuration (Allow multiple reconfigurations) - Enable to only be allowed to make one change to peripheral pin select settings

All these features are great and all but sheesh setting up a PIC32MZ before you can even do anything is a lot of work. That's why I use a skeleton and just copy it over each time. Another long winded explanation done, next time some actual code!

Here's the code

Tags: code