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:
1
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.
2
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);
GPIO_PinInterruptEnable(SW1_PIN);
Following the addition of the code above, add the function call.
TMR1_Start();
Note:
a
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.
b
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.
c
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.
d
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.
e
The function call GPIO_PinInterruptEnable enables the GPIO interrupt on a pin.
3
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)
{
if(SW1_Get() == SWITCH_PRESSED_STATE)
{
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)
{
if (event == DMAC_TRANSFER_EVENT_COMPLETE)
{
isUARTTxComplete = true;
}
}
4
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);
6
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");
TMR1_PeriodSet(PERIOD_1S);
}
else if(tempSampleRate == TEMP_SAMPLING_RATE_1S)
{
tempSampleRate = TEMP_SAMPLING_RATE_2S;
sprintf((char*)uartTxBuffer, "Sampling Temperature every 2 seconds \r\n");
TMR1_PeriodSet(PERIOD_2S);
}
else if(tempSampleRate == TEMP_SAMPLING_RATE_2S)
{
tempSampleRate = TEMP_SAMPLING_RATE_4S;
sprintf((char*)uartTxBuffer, "Sampling Temperature every 4 seconds \r\n");
TMR1_PeriodSet(PERIOD_4S);
}
else if(tempSampleRate == TEMP_SAMPLING_RATE_4S)
{
tempSampleRate = TEMP_SAMPLING_RATE_500MS;
sprintf((char*)uartTxBuffer, "Sampling Temperature every 500 ms \r\n");
TMR1_PeriodSet(PERIOD_500MS);
}
else
{
;
}
7
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);
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.
2
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 >