A quick update!

An update on my site

Hi there everyone and no one. I haven't updated this site for quite some time (10 months or so). Certainly the COVID-19 pandemic has changed the way we do work and changed the world but apart from that I really just started this site to share the knowledge of PIC32MZ that I had gained through years of trial and error. I was sick of seeing useless "read the manual" and "work it out for yourself" posts by arrogant and selfish people on the microchip forums and annoyed by the tons of errors I had found in the datasheets. But now that I've written about all the stuff I wanted to share I really don't know what more to write about :)

I'm keeping the site up and I do hope it can provide a useful reference to the few people that need it. I'll also be uploading some of the schematics of stuff I've been working on in the next week or two.

Works and life have kept me busy and so I've been putting off replying to posts ("I'll get to that tomorrow") but I'll get to them today and hopefully stop being so lazy :)

Apologies if I miss out on replying to anyone's older questions the notification system here sucks!

Tags: rambling

Choosing which pins to use on a PIC32MZ project

How I choose which pins to use on a PIC32MZ project

I got an interesting question in my comments yesterday asking how I go about choosing which pins to use when I do a PIC32MZ project. My answer is a bit long and I think it might be useful to other noobs like me so I'm releasing it as a post today. Please note: This isn't based on anything but my own experience with PIC32MZ projects, it is certainly not the only way to to things. It's just the way I do it myself.

Basically, I go through the following steps:

  • List all peripherals I will need for the project
  • Check which hidden secrets the peripherals have
  • Check errata to make sure they all work
  • Check which pins each peripheral needs
  • Actually choose the pins

List all peripherals I will need for the project

In the example I was given, it was 2 x I2S (using an internally generated reference clock), 3 x UART, 2 x I2C and 2 x SPI on a 144-pin PIC32MZ EFG chip. Furthermore, while I2C and SPI peripherals can both be connected to multiple devices, in the interests of speed (and for the explanation) I'm going to connect each device to a separate SPI and I2C peripheral. The aim will be simple: to map these peripherals to GPIO pins in a way that makes layout easy.

Check which hidden secrets the peripherals have

All the peripherals have certain things that must be kept in mind when they are used. They're not really secrets at all but they may not be immediately obvious when you first set out to use them. For the example, the "hidden secrets" are as follows:

SECRET 1
I2C peripherals are fixed and cannot be moved. Furthermore, I2C SDA and SCL pins require a 4.7k pullup resistor in order for them to meet the I2C standard and work reliably.

SECRET 2
I2S either works off of PBCLK2 or off of REFCLKO1 because REFCLKO1 controls all SPI (which I2S is a subset of). It cannot just use any reference clock. For audio and the frequencies it uses, REFCLKO1 is much more useful. Furthermore, REFCLKO1 is set up by PPS, which further limits out options to the following:

PIC32MZ - REFOCLK1 pins

SECRET 3
Although most pins for SPI peripherals can be moved around at will, the clock pins (SCK1 ~ SCK6) cannot be moved. Luckily, if we control the Chip Select (CS) pin of the device ourselves, we can gain a bit more flexibility.

SECRET 4
As I detailed in a post in the past, in order to extract 50Mhz from the SPI port you can only use SPI2 or SPI3 and even then only on specific pins, as follows:

PIC32MZ - SPI speed trick

Because of this, I'm going to be putting the SD card on SPI2 (but you could put the LCD there for 50Mhz too if you wish).

Check errata to make sure they all work

Unlike the 32MZ EC series, which was a horrible mess full of bugs, the EF series has relatively few errata. There is, however, one that looks concerning. It's listed as "The I2C module does not function reliably under certain conditions."
It turns out that this problem occurs when we use I2C at a rate of more than 100kHz and have constant I2C transfers of more than 500 bytes. The solution from Microchip, as it has been since the PIC32MX days, is to "bit bang". Which means to implement I2C by yourself and not use their peripheral. That's pretty poor.
In practice I haven't encountered this yet but it is something to bear in mind.

Amusingly, the UART has the following errata: "The UART automatic baud rate feature is intended to set the baud rate during run-time based on external data input. However, this feature does not function." Great work, Microchip. Great work.

Check which pins each peripheral needs

Straightforward stuff here. I'll list the ones relevant to my example.

Each SPI needs:

  • MISO (SDI)
  • MOSI (SDO)
  • CS
  • CLK

Each I2C needs:

  • SDA
  • SCL

Each UART needs:

  • RX
  • TX

Each I2S needs:

  • MCLK (REFCLKO1)
  • MOSI (SDO)
  • SCK
  • LRCK (for which I use Slave Select (SS))

Actually choose the pins

OK, now the part that we actually wanted to do. I like to keep the peripherals separated if possible to make layout easier as I routinely use double-sided boards. For this step I look at two parts of the datasheet, namely the pinout of the device and the PPS Input and Output sections. Bearing that in mind, for this example I'd use something like this:

First, on the one "side" of the PIC32MZ EF 144-pin devices, we have the RH and RK ports, which are totally useless from a PPS perspective. There are also no I2C peripherals on that particular side. However, there is a potential output for REFCLKO1 and three SPI peripherals, so I've put the two I2S ports there like this:

SPI3 (I2S):

  • MOSI3 = RD14
  • SCK3 = RB14
  • SS3 = RF12 (used as LRCK)
  • REFOCLK1 = RD15

SPI5 (I2S):

  • MOSI5 = RB9
  • SCK5 = RF13
  • SS5 = RB8 (used as LRCK)
  • REFOCLK1 = RD15

As noted earlier, I need two SPI peripherals and one of them must be on SPI2, using RB3 and RB5. There is one I2C peripheral on this same side. I've laid it out like this:

SPI2 (50Mhz):

  • SCK2 = RG6
  • MISO2 = RB3
  • MOSI2 = RB5
  • SS2 = RG9 (Chip Select)

I2C4:

  • SDA4 = RG7
  • SCL4 = RG8

We still need one more I2C and one more SPI peripheral, which I've done as follows:

I2C2:

  • SDA2 = RA3
  • SCL2 = RA2

SPI4:

  • SCK4 = RD10
  • MISO4 = RD11
  • MOSI4 = RD0
  • SS4 = RH12 [See below]

I've chosen RH12 as SS4 (or Chip Select) but it cannot be used as SS4 in the PPS setting. I've done this because I plan to use this for the LCD, which needs me to manually control the Chip Select (CS) line and there are also no convenient PPS mappings for SS4 nearby.

OK, mostly done now. We now need 3 UARTs, which I've done as follows:

UART3:

  • U3TX = RD6
  • U3RX = RD7

UART4:

  • U4TX = RD4
  • U4RX = RD5

UART5:

  • U5TX = RF0
  • U5RX = RF1

In the end, it looks like this in Eagle (click to zoom):

PIC32MZ - Choosing pins PIC32MZ - Choosing pins

Hope this will be of use to someone :)

Tags: hardware, planning, circuits

Shared pins on the PIC32MZ's ADC

Cautionary note about shared analog inputs when choosing which analog inputs to use

Last time I briefly went over how to use the alternative analog inputs on ADCs 0 to 4. It went like this:

  • set SH0ALT to 0 / 1 to use AN0 / AN45 on ADC0
  • set SH1ALT to 0 / 1 to use AN1 / AN46 on ADC1,
  • set SH2ALT to 0 / 1 to use AN2 / AN47 on ADC2,
  • set SH3ALT to 0 / 1 to use AN3 / AN48 on ADC3 or
  • set SH4ALT to 0 / 1 to use AN4 / AN49 on ADC4 respectively.

At the time I didn't realise the full implications of that. I was working on a project yesterday and saw this:

PIC32MZ ADC MPLAB X Code Complete!

I was trying to use AN45, but there didn't seem to be a flag to check to see if the ADC conversion had completed, so I went to check the datasheet. Sure enough, I found this:

PIC32MZ ADC ADCDATA!

What I was expecting was ADCDATA45, ADCDATA46, ADCDATA47, ADCDATA48 and ADCDATA49, but it ended at ADCDATA44. Then suddenly I remember the pins could be used as alternative inputs and went back to look at the ADC block diagram again:

PIC32MZ ADC - Alternative Inputs Block Diagram!

That confirmed it for me. You can use AN45 as an input OR you can use AN0 as an input, and they will both work on ADC0. However, you can not use them at the same time!

TECHNICALLY
Yes, I'm sure you could keeping setting and clearing SH0ALT to use them both but that is not what I'm referring to. I'm referring to a situation where you look at the datasheet and think you can attach signal A to AN0 and signal B to AN45 and read them in at the same time.
/TECHNICALLY

So how to use AN45 then? Well, you set SH0ALT to 1 and then write the code as if you are actually using AN0. That is, you still use all the ADC0 bit settings and flags (like ADCDATA0, ARDY0, TRGSRC0, etc.) as if you were using AN0 but the PIC32MZ will know that you are actually using AN45 instead of AN0 because you've set SH0ALT to 1.

TL;DR ADC inputs AN45, AN46, AN47, AN48 and AN49 are shared with ADC inputs AN0, AN1, AN2, AN3 and AN4 respectively. You can use, for example, either AN0 or AN45 but you can not use both at the same time for different signals.

And yes, it seems obvious now but I think this is a potentially very dangerous design trap that you might fall into if you don't know about it so I'm uploading it in the hopes that someone who needs this will find it.

Tags: code, ADC

ADC on the PIC32MZ

So you want to use the ADC

If you've come from using the PIC32MX series, you'll remember that using the ADC was a simple affair. You could set it up in a few lines and then BAM it worked. And wow, the specs of the PIC32MX were merely:

  • Fast and Accurate 16 channel 10-bit ADC
  • Max 1 Mega sample per second at +/- 1LSB, conversion available during SLEEP & IDLE

But now with the MZ, take a look at this:

  • 12-bit ADC Module
  • 18 Msps rate with six Sample and Hold (S&H) circuits (five dedicated and one shared)
  • Up to 48 analog inputs
  • Multiple trigger sources
  • Temperature sensor with ±2°C accuracy

Whooooooooooaaaaaa! 18 times the speed, 3 times the channels and even a temperature sensor. That is so cool! So you undoubtedly thought that, like the PIC32MX. the PIC32MZ would be simple too, maybe even use the same code. But, like everything else with the MZ, you're met with the cold, hard reality that

Microchip
hates
you

and doesn't want you to use one of their fastest PIC chips. You see, apart from the minor issue of the temperature sensor not working, at all, on any device, there's the matter of the insane way they've gone about implementing these 48 channels. To illustrate, here's what the map of the PIC32MX ADC hardware looks like:

PIC32MX - ADC Block Diagram

OK, so it's your typical Microchip block diagram. A bit confusing in places (and what's happening with AN1 there?) but it's not too bad. Now let's take a look at the new map for the PIC32MZ:

PIC32MZ ADC block diagram!

Ha. Ha. Ha. What a cruel joke. And this is the point where you run back to the safety of Harmony, only to realise multiple releases of Harmony also have bugs with the ADC code. Not even Microchip can get it working. But it's not just that it looks complicated, it actually is more complicated to use. But not impossible. In theory, this gives us more flexibility but the trade-off is that what used to take 3 lines to set up now takes a good deal more.

Secret Knowledge 1 - The ADC inputs are divided into classes

What you absolutely cannot tell from looking at that diagram is the on the PIC32MZ the ADC inputs are divided into the following classes:

  • Class 1 - These inputs each have their own dedicated sample and holding hardware and can be triggered individually, are always sampling and will be converted as soon as you trigger them, independent of each other.
  • Class 2 - These share ONE sample and hold unit, specificially ADC7, and can also be triggered individually but will only start sampling once you trigger them and even then, they have to take turns using ADC7 to do so.
  • Class 3 - These also share ADC7

Furthermore, the inputs are divided as follows (for the PIC32MZ EF series):

  • Class 1 = AN0 to AN4
  • Class 2 = AN5 to AN31
  • Class 3 = AN32 to AN44

Furthermore, the 18Msps speed is a bit fat bit of marketing speak as shown here:

PIC32MZ real ADC speeds

Even in their own datasheet they can't get 18msps, the closest they get is 16.67msps and that's only is you trigger 4 class 1 inputs at once, and even then only if they're in 6-bit mode. Ugh. Marketing sucks. But it's not all bad news!

How to use class 1 inputs on the PIC32MZ ADC

Today I'm going to just cover how to use class 1 inputs. Shockingly, the example in the ADC datasheet (DS60001344B) actually mostly works (apart from some key things that need to be set up first). Well, the class 1 one does. The class 2 one has typos in some of the register names. Because of course it does :)

In said example, AN0, AN1 and AN2 are set up and read into an array of integers called result[]. Let's take a look at said code from the datasheet:

int main(int argc, char** argv) 
{
    int result[3];
    /* initialize ADC calibration setting */
    ADC0CFG = DEVADC0;
    ADC1CFG = DEVADC1;
    ADC2CFG = DEVADC2;
    ADC3CFG = DEVADC3;
    ADC4CFG = DEVADC4;
    ADC7CFG = DEVADC7;
    /* Configure ADCCON1 */
    ADCCON1 = 0; // No ADCCON1 features are enabled including: Stop-in-Idle, turbo,
    // CVD mode, Fractional mode and scan trigger source.
    /* Configure ADCCON2 */
    ADCCON2 = 0; // Since, we are using only the Class 1 inputs, no setting is
    // required for ADCDIV
    /* Initialize warm up time register */
    ADCANCON = 0;
    ADCANCONbits.WKUPCLKCNT = 5; // Wakeup exponent = 32 * TADx
    /* Clock setting */
    ADCCON3 = 0;
    ADCCON3bits.ADCSEL = 0; // Select input clock source
    ADCCON3bits.CONCLKDIV = 1; // Control clock frequency is half of input clock
    ADCCON3bits.VREFSEL = 0; // Select AVDD and AVSS as reference source
    /* Select ADC sample time and conversion clock */
    ADC0TIMEbits.ADCDIV = 1; // ADC0 clock frequency is half of control clock = TAD0
    ADC0TIMEbits.SAMC = 5; // ADC0 sampling time = 5 * TAD0
    ADC0TIMEbits.SELRES = 3; // ADC0 resolution is 12 bits
    ADC1TIMEbits.ADCDIV = 1; // ADC1 clock frequency is half of control clock = TAD1
    ADC1TIMEbits.SAMC = 5; // ADC1 sampling time = 5 * TAD1
    ADC1TIMEbits.SELRES = 3; // ADC1 resolution is 12 bits
    ADC2TIMEbits.ADCDIV = 1; // ADC2 clock frequency is half of control clock = TAD2
    ADC2TIMEbits.SAMC = 5; // ADC2 sampling time = 5 * TAD2
    ADC2TIMEbits.SELRES = 3; // ADC2 resolution is 12 bits
    /* Select analog input for ADC modules, no presync trigger, not sync sampling */
    ADCTRGMODEbits.SH0ALT = 0; // ADC0 = AN0
    ADCTRGMODEbits.SH1ALT = 0; // ADC1 = AN1
    ADCTRGMODEbits.SH2ALT = 0; // ADC2 = AN2
    /* Select ADC input mode */
    ADCIMCON1bits.SIGN0 = 0; // unsigned data format
    ADCIMCON1bits.DIFF0 = 0; // Single ended mode
    ADCIMCON1bits.SIGN1 = 0; // unsigned data format
    ADCIMCON1bits.DIFF1 = 0; // Single ended mode
    ADCIMCON1bits.SIGN2 = 0; // unsigned data format
    ADCIMCON1bits.DIFF2 = 0; // Single ended mode
    /* Configure ADCGIRQENx */
    ADCGIRQEN1 = 0; // No interrupts are used
    ADCGIRQEN2 = 0;
    /* Configure ADCCSSx */
    ADCCSS1 = 0; // No scanning is used
    ADCCSS2 = 0;
    /* Configure ADCCMPCONx */
    ADCCMPCON1 = 0; // No digital comparators are used. Setting the ADCCMPCONx
    ADCCMPCON2 = 0; // register to '0' ensures that the comparator is disabled.
    ADCCMPCON3 = 0; // Other registers are “don't care”.
    ADCCMPCON4 = 0;
    ADCCMPCON5 = 0;
    ADCCMPCON6 = 0;
    /* Configure ADCFLTRx */
    ADCFLTR1 = 0; // No oversampling filters are used.
    ADCFLTR2 = 0;
    ADCFLTR3 = 0;
    ADCFLTR4 = 0;
    ADCFLTR5 = 0;
    ADCFLTR6 = 0;
    /* Set up the trigger sources */
    ADCTRGSNSbits.LVL0 = 0; // Edge trigger
    ADCTRGSNSbits.LVL1 = 0; // Edge trigger
    ADCTRGSNSbits.LVL2 = 0; // Edge trigger
    ADCTRG1bits.TRGSRC0 = 1; // Set AN0 to trigger from software.
    ADCTRG1bits.TRGSRC1 = 1; // Set AN1 to trigger from software.
    ADCTRG1bits.TRGSRC2 = 1; // Set AN2 to trigger from software.
    /* Early interrupt */
    ADCEIEN1 = 0; // No early interrupt
    ADCEIEN2 = 0;
    /* Turn the ADC on */
    ADCCON1bits.ON = 1;
    /* Wait for voltage reference to be stable */
    while(!ADCCON2bits.BGVRRDY); // Wait until the reference voltage is ready
    while(ADCCON2bits.REFFLT); // Wait if there is a fault with the reference voltage
    /* Enable clock to analog circuit */
    ADCANCONbits.ANEN0 = 1; // Enable the clock to analog bias
    ADCANCONbits.ANEN1 = 1; // Enable the clock to analog bias
    ADCANCONbits.ANEN2 = 1; // Enable the clock to analog bias
    /* Wait for ADC to be ready */
    while(!ADCANCONbits.WKRDY0); // Wait until ADC0 is ready
    while(!ADCANCONbits.WKRDY1); // Wait until ADC1 is ready
    while(!ADCANCONbits.WKRDY2); // Wait until ADC2 is ready
    /* Enable the ADC module */
    ADCCON3bits.DIGEN0 = 1; // Enable ADC0
    ADCCON3bits.DIGEN1 = 1; // Enable ADC1
    ADCCON3bits.DIGEN2 = 1; // Enable ADC2
    while (1) 
    {
        /* Trigger a conversion */
        ADCCON3bits.GSWTRG = 1;
        /* Wait the conversions to complete */
        while (ADCDSTAT1bits.ARDY0 == 0);
        /* fetch the result */
        result[0] = ADCDATA0;
        while (ADCDSTAT1bits.ARDY1 == 0);
        /* fetch the result */
        result[1] = ADCDATA1;
        while (ADCDSTAT1bits.ARDY2 == 0);
        /* fetch the result */
        result[2] = ADCDATA2;
        /*
        * Process results here
        *
        * Note: Loop time determines the sampling time since all inputs are Class 1.
        * If the loop time is small and the next trigger happens before the completion
        * of set sample time, the conversion will happen only after the sample time
        * has elapsed.
        *
        */
    }
    return (1);
}

OK...
...

70 lines of code to set up ADC0, ADC1 and ADC2. These are some big trade-offs but fine, fine, we'll take a look at it piece by piece. Important to note is that for some of this code the order of execution is very important so I recommend not changing it unless you know what you are doing. Also, on the EF devices there is no such thing as ADC5 and ADC6 so I've removed references to them from my code. With that, let's start:

/* initialize ADC calibration setting */
ADC0CFG = DEVADC0;
ADC1CFG = DEVADC1;
ADC2CFG = DEVADC2;
ADC3CFG = DEVADC3;
ADC4CFG = DEVADC4;
ADC7CFG = DEVADC7;

This sets up the 6 ADC units, ADC0 through ADC4 and ADC7, with their default values. Why you wouldn't do this automatically I don't know because on inspection of the datasheet ADCxCFG looks like this:

PIC32MZ ADC default config registers

So while yes, you can change these there seems to be no reason to. Just classic Microchip design crashing into reality again.

/* Configure ADCCON1 */
ADCCON1 = 0; // No ADCCON1 features are enabled including: Stop-in-Idle, turbo,
// CVD mode, Fractional mode and scan trigger source.
/* Configure ADCCON2 */
ADCCON2 = 0; // Since, we are using only the Class 1 inputs, no setting is
// required for ADCDIV

Something very important to note in the set up of ADCCON1 and ADCCON2. We are only using class 1 inputs so no clock division stuff needs to be set upbut if you are using class 2 or 3 you will have to set up these as they control various aspects of ADC7.

/* Initialize warm up time register */
ADCANCON = 0;
ADCANCONbits.WKUPCLKCNT = 5; // Wakeup exponent = 32 * TADx
/* Clock setting */
ADCCON3 = 0;
ADCCON3bits.ADCSEL = 0; // Select input clock source
ADCCON3bits.CONCLKDIV = 1; // Control clock frequency is half of input clock
ADCCON3bits.VREFSEL = 0; // Select AVDD and AVSS as reference source

ADCANCON controls how long the ADC modules need to "warm up" before they can be used for conversion. Thankfully, this number is common to all ADCs on the PIC32MZ devices and is always set to 5 (which = 32TADx). As we will see later, it also contains the "enable" bits for ADCs 0 through 7 and status bits for whether they are ready or not.
For us and our class 1 example, ADCCON3 controls what to use as a voltage reference, what the clock frequency is and whether the digital outputs for each ADC module, again 0 through 7, are enabled or not.

/* Select ADC sample time and conversion clock */
ADC0TIMEbits.ADCDIV = 1; // ADC0 clock frequency is half of control clock = TAD0
ADC0TIMEbits.SAMC = 5; // ADC0 sampling time = 5 * TAD0
ADC0TIMEbits.SELRES = 3; // ADC0 resolution is 12 bits
ADC1TIMEbits.ADCDIV = 1; // ADC1 clock frequency is half of control clock = TAD1
ADC1TIMEbits.SAMC = 5; // ADC1 sampling time = 5 * TAD1
ADC1TIMEbits.SELRES = 3; // ADC1 resolution is 12 bits
ADC2TIMEbits.ADCDIV = 1; // ADC2 clock frequency is half of control clock = TAD2
ADC2TIMEbits.SAMC = 5; // ADC2 sampling time = 5 * TAD2
ADC2TIMEbits.SELRES = 3; // ADC2 resolution is 12 bits

As we're using AN0, AN1 and AN2, we need to set up timing information and resolution information for ADCs 0 through 2. Nothing complicated here.

/* Select analog input for ADC modules, no presync trigger, not sync sampling */
ADCTRGMODEbits.SH0ALT = 0; // ADC0 = AN0
ADCTRGMODEbits.SH1ALT = 0; // ADC1 = AN1
ADCTRGMODEbits.SH2ALT = 0; // ADC2 = AN2

Hmmm... what's this? There is some limited flexibility with ADCs 0 through 4. While, by default, AN0 is set up to be connected to ADC0, you can change SH0ALT to 1 to make ADC0 use pin AN45 instead. Similarly, you can:

  • set SH1ALT to 0 / 1 to use AN1 / AN46,
  • set SH2ALT to 0 / 1 to use AN2 / AN47,
  • set SH3ALT to 0 / 1 to use AN3 / AN48 or
  • set SH4ALT to 0 / 1 to use AN4 / AN49 respectively.
/* Select ADC input mode */
ADCIMCON1bits.SIGN0 = 0; // unsigned data format
ADCIMCON1bits.DIFF0 = 0; // Single ended mode
ADCIMCON1bits.SIGN1 = 0; // unsigned data format
ADCIMCON1bits.DIFF1 = 0; // Single ended mode
ADCIMCON1bits.SIGN2 = 0; // unsigned data format
ADCIMCON1bits.DIFF2 = 0; // Single ended mode

The FRACT and SIGN bits together make up the format in which the ADC will return data to you, according to this table:

PIC32MZ ADC result format

For our purpose, we just want a 12-bit integer, so we want them both set to 0 (FRACT is contained within ADCCON1 which we previously set to 0).
DIFF, as the name suggests, allows us to select between single-ended or differential inputs, as follows:

PIC32MZ ADC single-ended or differential mode

For this example, we're just wanting to stick a potentiometer on AN0, AN1 and AN2 and measure between 3.3V and 0V so I'll be using single-ended inputs only.

/* Configure ADCGIRQENx */
ADCGIRQEN1 = 0; // No interrupts are used
ADCGIRQEN2 = 0;
/* Configure ADCCSSx */
ADCCSS1 = 0; // No scanning is used
ADCCSS2 = 0;

We are not using interrupts and we are not using scanning. That is, we will be manually triggering the ADCs every time we want to read values from them.

/* Configure ADCCMPCONx */
ADCCMPCON1 = 0; // No digital comparators are used. Setting the ADCCMPCONx
ADCCMPCON2 = 0; // register to '0' ensures that the comparator is disabled.
ADCCMPCON3 = 0; // Other registers are “don't care”.
ADCCMPCON4 = 0;
ADCCMPCON5 = 0;
ADCCMPCON6 = 0;
/* Configure ADCFLTRx */
ADCFLTR1 = 0; // No oversampling filters are used.
ADCFLTR2 = 0;
ADCFLTR3 = 0;
ADCFLTR4 = 0;
ADCFLTR5 = 0;
ADCFLTR6 = 0;

We are not wanting any of the fancy digital comparator functionality and we are not wanting to use any of the oversampling filters. Just let us use the ADC already!

/* Set up the trigger sources */
ADCTRGSNSbits.LVL0 = 0; // Edge trigger
ADCTRGSNSbits.LVL1 = 0; // Edge trigger
ADCTRGSNSbits.LVL2 = 0; // Edge trigger
ADCTRG1bits.TRGSRC0 = 1; // Set AN0 to trigger from software.
ADCTRG1bits.TRGSRC1 = 1; // Set AN1 to trigger from software.
ADCTRG1bits.TRGSRC2 = 1; // Set AN2 to trigger from software.

From what I can tell, ADCTRGSNS (ADC Trigger Sensitivity) is set to 0 to trigger on a positive edge (a transition from 0 to 1) or set to 1 to constantly trigger as long as the trigger is set to 1.
For ADCTRG1 through 8 (ADC Trigger) we need to set up how the ADC will be triggered. As we will be triggering it manually from software, we need to set AN0, AN1 and AN2's triggers to 1 to tell the PIC32MZ we will be triggering it from software.

/* Early interrupt */
ADCEIEN1 = 0; // No early interrupt
ADCEIEN2 = 0;

No interrupts. Not today. No.

/* Turn the ADC on */
ADCCON1bits.ON = 1;
/* Wait for voltage reference to be stable */
while(!ADCCON2bits.BGVRRDY); // Wait until the reference voltage is ready
while(ADCCON2bits.REFFLT); // Wait if there is a fault with the reference voltage

Having finally finished the ADC setup, we can turn it on. Once it's on, we need to wait for voltages to stabilise, by checking the BGVRRDY and REFFLT flags) before we can actually enable the inputs and use it.

/* Enable clock to analog circuit */
ADCANCONbits.ANEN0 = 1; // Enable the clock to analog bias
ADCANCONbits.ANEN1 = 1; // Enable the clock to analog bias
ADCANCONbits.ANEN2 = 1; // Enable the clock to analog bias
/* Wait for ADC to be ready */
while(!ADCANCONbits.WKRDY0); // Wait until ADC0 is ready
while(!ADCANCONbits.WKRDY1); // Wait until ADC1 is ready
while(!ADCANCONbits.WKRDY2); // Wait until ADC2 is ready

We are so close now! We enable the clock bias circuitry for each of the ADCs (ANEN0 is ADC0, ANEN1 is ADC1 and ANEN2 is ADC2) and then wait for the ADCs to be ready. And then, finally:

/* Enable the ADC module */
ADCCON3bits.DIGEN0 = 1; // Enable ADC0
ADCCON3bits.DIGEN1 = 1; // Enable ADC1
ADCCON3bits.DIGEN2 = 1; // Enable ADC2

The final step in the setup is to set the Digital Enable DIGEN to 1 . From this point on, ADC0, ADC1 and ADC2 are always sampling from AN0, AN1 and AN2 and are waiting for us to trigger them before they convert the sampled values into the 12-bit format we told them to. This is accomplished by:

/* Trigger a conversion */
ADCCON3bits.GSWTRG = 1;
/* Wait the conversions to complete */
while (ADCDSTAT1bits.ARDY0 == 0);
/* fetch the result */
result[0] = ADCDATA0;
while (ADCDSTAT1bits.ARDY1 == 0);
/* fetch the result */
result[1] = ADCDATA1;
while (ADCDSTAT1bits.ARDY2 == 0);
/* fetch the result */
result[2] = ADCDATA2;

Basically, once you trigger the conversion by setting GSWTRG to 1, ADC0, ADC1 and ADC2 will simultaneously start converting their sampled values to something we can use. Once they're done converting, we can read the values from the appropriate register (ADCDATA0 etc).

I tried the example, it doesn't work, you suck

Indeed. If you try the example as above, it may not work. Why? Because for each of those analog inputs, AN0 to AN2, you have to both set the TRIS value for that port to 1 and set the ANSEL bit for each pin to 1 too. Thankfully, these are the defaults so there's no huge problem there but it is something to be aware of. For reference, this can be done like this:

TRISBbits.TRISB2 = 1; // AN2 = RB2, set it as an input
ANSELBbits.ANSB2 = 1; // Set AN2 to analog mode

My example code, available below, adds the fuses for using FRC at 200Mhz and sets up the wait states and cache. Other than that, it's the same as the example straight from the datasheet.

Here's the code.

Tags: code, ADC