Harmony v3 Peripheral Libraries on PIC32MZ EF: Step 5

Step 5: Add application code to the project

The application is already developed (partially) and is available in the main_pic32mz.c file under <your unzip folder>/pic32mzef_getting_started/dev_files/pic32mz_ef_curiosity_v2. The main_pic32mz.c file contains the application logic. It also contains placeholders that you will populate with the necessary code in the next step.

  • Go to the pic32mzef_getting_started/dev_files/pic32mz_ef_curiosity_v2 folder and copy the pre-developed main_pic32mz.c file.
  • Replace (over-write) the main_pic32mz.c file of your project available at <Your project folder>/pic32mzef_getting_started/firmware/src with the copied file.
  • Open main_pic32mz.c in MPLAB® X IDE and add the application code by following the steps below:


Under the main_pic32mz.c file, in function main, notice the call to function SYS_Initialize. The generated function SYS_Initialize initializes all the peripheral modules used in the application (configured through the MPLAB Code Configurator (MCC)).

Tip: Press the CTRL key and left click on the SYS_Initialize function. The click will open the implementation for the SYS_Initialize function as shown in the accompanying image.


Note: The MCC adds some of the system modules to the project by default and the corresponding default code also gets generated. EVIC_Initialize is one such example.


In the main_pic32mz.c function, below SYS_Initialize(), add the following code to register callback event handlers.

I2C1_CallbackRegister(i2cEventHandler, 0);
DMAC_ChannelCallbackRegister(DMAC_CHANNEL_0, UARTDmaChannelHandler, 0);
TMR1_CallbackRegister(tmr1EventHandler, 0);
GPIO_PinInterruptCallbackRegister(SW1_PIN, SW1_User_Handler, 0);

Following the addition of the code above, add the function call.




The function call I2C1_CallbackRegister registers a callback event handler with the I²C Peripheral Library (PLIB). The event handler is called by the I²C PLIB when the I²C transfer is complete.


The function call DMAC_ChannelCallbackRegister registers a callback event handler with the Direct Memory Access (DMA) PLIB. The callback event handler is called by the DMA PLIB when the DMA transfer (of temperature sensor data to the serial terminal) is complete.


The function call TMR1_CallbackRegister registers a Timer1 callback event handler with the TMR1 PLIB. The callback event handler is called by the TMR1 PLIB when the configured time period has elapsed.


The function call GPIO_PinInterruptCallbackRegister registers a General Purpose Input/Output (GPIO) callback event handler with the GPIO PLIB. The callback event handler is called by the GPIO PLIB when the user presses the switch SW1.


The function call GPIO_PinInterruptEnable enables the GPIO interrupt on a pin.


Implement the registered callback event handlers for TMR1, I²C, Universal Asynchronous Receiver Transmitter (UART), and GPIO PLIBs by adding the following code before:
main() function in main_pic32mz.c

static void SW1_User_Handler(GPIO_PIN pin, uintptr_t context)
        changeTempSamplingRate = true;      

static void tmr1EventHandler (uint32_t intCause, uintptr_t context)
    isTmr1Expired = true;                              

static void i2cEventHandler(uintptr_t contextHandle)
    if (I2C1_ErrorGet() == I2C_ERROR_NONE)
        isTemperatureRead = true;

static void UARTDmaChannelHandler(DMAC_TRANSFER_EVENT event, uintptr_t contextHandle)
        isUARTTxComplete = true;


Add the code below to submit an I²C transfer request to read the temperature sensor value when the configured time period (default 500 milliseconds) has elapsed. The I²C PLIB calls back the callback event handler (registered in Step 2) when the submitted request is complete.

isTmr1Expired = false;
I2C1_WriteRead(TEMP_SENSOR_SLAVE_ADDR, &i2cWrData, 1, i2cRdData, 2);


Add the code below to prepare the received temperature value from the sensor to be printed on the serial terminal.

temperatureVal = getTemperature(i2cRdData);
sprintf((char*)uartTxBuffer, "Temperature = %02d F\r\n", temperatureVal);


Add the code below to implement the change of sampling rate and prepare a message for the same on the serial terminal when the user presses the switch SW1.

changeTempSamplingRate = false;
if(tempSampleRate == TEMP_SAMPLING_RATE_500MS)
    tempSampleRate = TEMP_SAMPLING_RATE_1S;
    sprintf((char*)uartTxBuffer, "Sampling Temperature every 1 second \r\n");
else if(tempSampleRate == TEMP_SAMPLING_RATE_1S)
    tempSampleRate = TEMP_SAMPLING_RATE_2S;
    sprintf((char*)uartTxBuffer, "Sampling Temperature every 2 seconds \r\n");        
else if(tempSampleRate == TEMP_SAMPLING_RATE_2S)
    tempSampleRate = TEMP_SAMPLING_RATE_4S;
    sprintf((char*)uartTxBuffer, "Sampling Temperature every 4 seconds \r\n");        
else if(tempSampleRate == TEMP_SAMPLING_RATE_4S)
   tempSampleRate = TEMP_SAMPLING_RATE_500MS;
   sprintf((char*)uartTxBuffer, "Sampling Temperature every 500 ms \r\n");        


Add code to transfer the buffer containing either:

  • the latest temperature value in the format “Temperature = XX F\r\n”, or
  • the message mentioning the change of sampling rate over UART using DMA.
DCACHE_CLEAN_BY_ADDR((uint32_t)uartTxBuffer, sizeof(uartTxBuffer));
DMAC_ChannelTransfer(DMAC_CHANNEL_0, (const void *)uartTxBuffer, strlen((const char*)uartTxBuffer), (const void *)&U6TXREG, 1, 1);


In the above code snippet, the Application Programming Interface (API) DCACHE_CLEAN_BY_ADDR, is called to address the cache coherency issue seen on the PIC32MZ family of MCUs (due to the default Write Back and Write Allocate cache policy set in the start-up code supplied in the development tools). Calling the API DCACHE_CLEAN_BY_ADDR copies the data from the cache memory to the main memory, thereby ensuring that the DMA peripheral uses the updated values in the write buffer.

The cache coherency issue is inevitable on applications running on MCUs that have cacheable memory regions and using the DMA for data transfer operations. This is because the CPU may perform read/write from the cache while the DMA, on the other hand, transfers data between the peripheral and physical memory.
The cache coherency issue observed in this application is shown in the accompanying image.


The above example is a memory-to-peripheral transfer (DMA reads from SRAM and writes to the peripheral). In this example, the CPU first populates the DMA write buffer with the data to be written (ABCDEF) to the peripheral. However, depending on the cache policy (Write Back and Write Allocate), the DMA write buffer may be available in the data cache. Hence, the DMA write buffer in the main memory still contains the old data (123456). When the DMA is triggered, the DMA reads the DMA write buffer from the main memory (123456). As a result, the DMA might end up transferring stale data to the peripheral.

One of the ways to address the cache coherency issue is to use the cache maintenance APIs from the CACHE PLIB.

The application uses the DCACHE_CLEAN_BY_ADDR API to flush the write buffer from the cache to the SRAM so that the DMA gets the latest data to be transferred to the Universal Synchronous Asynchronous Receiver Transmitter (USART).

In the above example, to resolve the cache coherency issue, the CPU first populates the DMA write buffer and then issues a cache clean command to flush the contents of the data cache (ABCDEF) into the main memory. The API DCACHE_CLEAN_BY_ADDR can be used to perform a cache clean operation. When the DMA is triggered, the DMA reads the DMA write buffer from the main memory which now contains the updated data (ABCDEF).
The accompanying screenshot shows how the cache coherency issues are addressed by using the cache maintenance APIs.



Another way to address the cache coherency issue on PIC32MZ devices is by using the coherent variable attribute.

For example,

unsigned int __attribute__((coherent)) buffer[1024];

In the above code snippet, the compiler allocates (at link time) the 1024 element in the non-cacheable memory region KSEG1.
Following this method will not have the performance benefit of using the cache.


You are now ready to build the code.

Next Step >

© 2024 Microchip Technology, Inc.
Notice: ARM and Cortex are the registered trademarks of ARM Limited in the EU and other countries.
Information contained on this site regarding device applications and the like is provided only for your convenience and may be superseded by updates. It is your responsibility to ensure that your application meets with your specifications. MICROCHIP MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND WHETHER EXPRESS OR IMPLIED, WRITTEN OR ORAL, STATUTORY OR OTHERWISE, RELATED TO THE INFORMATION, INCLUDING BUT NOT LIMITED TO ITS CONDITION, QUALITY, PERFORMANCE, MERCHANTABILITY OR FITNESS FOR PURPOSE. Microchip disclaims all liability arising from this information and its use. Use of Microchip devices in life support and/or safety applications is entirely at the buyer's risk, and the buyer agrees to defend, indemnify and hold harmless Microchip from any and all damages, claims, suits, or expenses resulting from such use. No licenses are conveyed, implicitly or otherwise, under any Microchip intellectual property rights.