Harmony v3 Peripheral Libraries on PIC32MX 470: Step 5

Step 5: Add Application Code To the Project

The application is already partially developed and is available in the main_pic32mx470.c file under
<your unzip folder>/pic32mx470_getting_started/dev_files/pic32mx470_curiosity. The bme280_driver.c file contains the driver code to read temperature from the BME280 sensor. The main_pic32mx470.c file contains the application logic. It also contains placeholders that you will populate with necessary code in the next step.

  • Go to the pic32mx470_getting_started/dev_files/pic32mx470_curiosity folder and copy the pre-developed main_pic32mx470.c file.
  • Replace the main_pic32mx470.c file of your project available at <Your project folder>/pic32mx470_getting_started/firmware/src by over-writing it with the copied file.
  • Open main_pic32mx470.c in MPLAB® X IDE and add the application code by following the steps below:


Add bme280_driver.c file into Source Files, located in the Projects tab.

  • Go to the pic32mx470_getting_started/dev_files/pic32mx470_curiosity folder and copy the pre-developed bme280_driver.c, bme280_driver.h, and bme280_definitions.h files.
  • Paste these bme280_driver.c, bme280_driver.h, and bme280_definitions.h files into your project source files, in the <Your project folder>/pic32mx470_getting_started/firmware/src folder.
  • In the Projects tab, right click on Source Files to add the existing pre-developed bme280_driver.c file.
  • Select the bme280_driver.c file from the <Your project folder>/pic32mx470_getting_started/firmware/src folder.
  • Click Select and the bme280_driver.c file will be added into project source files.


Under the main_pic32mx470.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, which is configured through MPLAB Harmony Configurator (MHC).

Tip: Press the CTRL key and left click on the SYS_Initialize function. This will open the implementation for the SYS_Initialize function as shown below.


Note: The EVIC_Initialize is a system-specific initialization function. MHC adds this module by default to the project graph and generates code. This module will be initialized to user configurations if the user configures it explicitly.


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

I2C1_CallbackRegister(I2CBufferEventHandler, 0);
DMAC_ChannelCallbackRegister(DMAC_CHANNEL_0, UARTDmaChannelHandler, 0);
TMR2_CallbackRegister(tmr2EventHandler, 0);
GPIO_PinInterruptCallbackRegister(S1_PIN, S1_User_Handler, 0);

Following the addition of the code above, add the function call to initialize the BME280 sensor.


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 serial terminal) is complete.


The function call TMR2_CallbackRegister registers a Timer1 callback event handler with the TMR2 PLIB. The callback event handler is called by the TMR2 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 S1.


The function call GPIO_PinInterruptEnable enables GPIO interrupt on a pin.


The function call BME280Sensor_Initialize sets the callbacks to read and write BME280 sensor registers, soft resets the BME280 sensor to start the read and write sensor values, and configures the oversampling and power modes. It also reads the calibration parameters to compensate the sensor outputs like humidity, pressure, and temperature.


The function call TMR2_Start is to start the Timer 2 peripheral.


Implement the registered callback event handlers for TMR2, I²C, Universal Asynchronous Receiver Transmitter (UART), and General Purpose Input/Output (GPIO) PLIBs by adding the following code before the main() function in main_pic32mx470.c.

static void S1_User_Handler(GPIO_PIN pin, uintptr_t context)
    if(S1_Get() == SWITCH_PRESSED_STATE)
        changeTempSamplingRate = true;      

static void tmr2EventHandler (uint32_t intCause, uintptr_t context)
    isTmr2Expired = true;                              

static void I2CBufferEventHandler(uintptr_t contextHandle)
    if (I2C1_ErrorGet() == I2C_ERROR_NONE)
        BME280SensorData.isBufferCompleteEvent = true;

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


Implement the BME280Sensor_Initialize() function after callback event handler functions in main_pic32mx470.c.

void BME280Sensor_Initialize(void)
    BME280SensorData.temperature   = 0;
    BME280SensorData.slaveID        = BME280_I2C_ADDRESS;
    BME280SensorData.isBufferCompleteEvent = false;

    /* Register with BME280 sensor */        

    /* 100 m.sec delay */

    if (BME280_CHIP_ID != BME280_ID_Get())
        while(1);       /* Error Occurred */
    BME280_SetOversampling(BME280_PARAM_TEMP, BME280_OVERSAMPLING_1X);


Implement the BME280Sensor_WriteReg(), BME280Sensor_ReadReg(), and BME280Sensor_Read() functions before the BME280Sensor_Initialize() function in main_pic32mx470.c.

static bool BME280Sensor_WriteReg(uint8_t wrAddr, uint8_t wrData) 
    BME280SensorData.txBuffer[0]            = wrAddr;
    BME280SensorData.txBuffer[1]            = wrData;
    BME280SensorData.isBufferCompleteEvent  = false;

    I2C1_Write(BME280SensorData.slaveID, BME280SensorData.txBuffer, 2);

    while(false == BME280SensorData.isBufferCompleteEvent);

    return true;

static uint8_t BME280Sensor_ReadReg(uint8_t rAddr) 
    BME280SensorData.txBuffer[0]            = rAddr;
    BME280SensorData.isBufferCompleteEvent  = false;

    I2C1_WriteRead(BME280SensorData.slaveID, BME280SensorData.txBuffer, 1, BME280SensorData.rxBuffer, 1);

    while(false == BME280SensorData.isBufferCompleteEvent);

    return BME280SensorData.rxBuffer[0];    

static bool BME280Sensor_Read(uint8_t rAddr, uint8_t* const pReadBuffer, uint8_t nBytes) 
    BME280SensorData.txBuffer[0]            = rAddr;
    BME280SensorData.isBufferCompleteEvent  = false;

    I2C1_WriteRead(BME280SensorData.slaveID, BME280SensorData.txBuffer, 1, pReadBuffer, nBytes);

    while(false == BME280SensorData.isBufferCompleteEvent);

    return true;



The function BME280Sensor_WriteReg is used to write the sensor configuration values to the sensor registers through the I2C1_Write function.


The function BME280Sensor_ReadReg is used to read the sensor register values from the sensor through the I2C1_Read function.


The function BME280Sensor_Read is used to read the sensor uncompensated values from the sensor through the I2C1_Read function. In the step 7 infobox, the about uncompensated values are explained.

In all these above functions, the application will wait to complete the I²C read or write operation. The I²C read or write operation completion flag will be set in the I2CBufferEventHandler callback function.


Using the screenshot below, add the code to submit a temperature read transfer request to read temperature sensor values when the configured time period (default 500 milliseconds) has elapsed. It will read uncompensated temperature values from the sensor when the submitted request is complete.

isTmr2Expired = false;

Using the screenshot below, add the code to calculate compensated temperature value. The compensated temperature is in DegC (°C) while the resolution is 0.01 °C.

BME280SensorData.temperature   = BME280_GetTempReading();

As per the BME280 sensor datasheet, each sensing element behaves differently due to sensing element non-linearity behaviour (any sensing element behaviour will be affected due to the environmental parameters like height, altitude, and temperatures. This is called non-linearity behaviour). These values are called as uncompensated sensor values. To compensate or to correct this non-linearity behaviour of the sensor output data using compensation formulas, please refer to the BME280 datasheet for the appropriate formulas.


Using the screenshot below, add the code to convert from Celcius to Fahrenheit (°C to °F) (°F = (°C × 9/5) + 32).

temperatureVal = ((BME280SensorData.temperature * 9 / 5) * 0.01) + 32;

Using the screenshot below, add the code to prepare the received temperature value from the sensor to be shown on the serial terminal.

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


Using the screenshot below, add the code to implement the change of sampling rate and prepare a message for the change on the serial terminal when the user presses the S1 switch.

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");        


Using the screenshot below, add the 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.
DMAC_ChannelTransfer(DMAC_CHANNEL_0, (const void *)uartTxBuffer, strlen((const char*)uartTxBuffer), (const void *)&U2TXREG, 1, 1);

You are now ready to build the code!

Next Step >

© 2020 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.