Harmony v3 Peripheral Libraries on PIC32MK GP: Step 5

Step 5: Add Application Code to the Project

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

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


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

  • Go to the pic32mk_getting_started/dev_files/pic32mk_gp_db 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>/pic32mk_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>/pic32mk_getting_started/firmware/src folder.
  • Once selected, the bme280_driver.c file will be added into the project source files.


Under the main_pic32mk.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 your configurations, if you configure it explicitly.


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

SPI6_CallbackRegister(SPIBufferEventHandler, 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 given above, set the Transmit Interrupt Mode Selection bits. This is needed to work around a known erroneous behavior of UART with DMAC. This issue is described in an infobox under Step 10.


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


After the addition of the code given above, add the function call.

  • The function call SPI6_CallbackRegister registers a callback event handler with the SPI Peripheral Library (PLIB). The event handler is called by the SPI PLIB when the SPI 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 GPIO callback event handler with the GPIO PLIB. The callback event handler is called by the GPIO PLIB when you press the S1 switch.
  • The function call GPIO_PinInterruptEnable enables GPIO interrupt on a pin.
  • The function call BME280Sensor_Initialize sets up the callbacks to read and write BME280 sensor registers, to soft reset 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 starts the Timer 2 peripheral.


Implement the registered callback event handlers for TMR2, SPI, UART, and GPIO PLIBs by adding the following code before the main() function in main_pic32mk.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 SPIBufferEventHandler(uintptr_t context)
    BME280SensorData.isBufferCompleteEvent = true;

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


Implement the BME280Sensor_Initialize() function after callback event handlers' functions in main_pic32mk.c.

void BME280Sensor_Initialize(void)
    BME280SensorData.temperature   = 0;
    BME280SensorData.slaveID        = BME280_SPI_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_pic32mk.c.

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

    SPI6_Write(BME280SensorData.txBuffer, 2);

    while(false == BME280SensorData.isBufferCompleteEvent);

    return true;

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

    SPI6_WriteRead(BME280SensorData.txBuffer, 1, BME280SensorData.rxBuffer, 2);

    while(false == BME280SensorData.isBufferCompleteEvent);

    return BME280SensorData.rxBuffer[1];    

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

    SPI6_WriteRead(BME280SensorData.txBuffer, 1, BME280SensorData.rxBuffer, (nBytes + 1));

    while(false == BME280SensorData.isBufferCompleteEvent);
    memcpy(pReadBuffer, &BME280SensorData.rxBuffer[1], nBytes);

    return true;
  • The function BME280Sensor_WriteReg writes the sensor configuration values to sensor registers through the SPI6_Write function.
  • The function BME280Sensor_ReadReg reads the sensor register values from the sensor registers through the SPI6_Read function.
  • The function BME280Sensor_Read reads the sensor uncompensated values from sensor registers through the SPI6_Read function. The infobox in Step 7 provides an explanation on uncompensated values.

In all these functions, the application will wait until the SPI read or write operation is complete. The SPI read or write operation completion flag will be set in the SPIBufferEventHandler callback function.


Add the code given below to submit a temperature read transfer request to read temperature sensor value when the configured time period (defaulted to 500 milliseconds) has elapsed. It will read uncompensated temperature values from the sensor when the submitted request is complete.

isTmr2Expired = false;

Add the code given below to calculate compensated temperature value. The compensated temperature is in degrees Celsius (°C) and its 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 behavior; that is, any sensing element will be affected by environmental parameters such as height, altitude, and temperatures. These values are called uncompensated sensor values. The information regarding compensating or correcting this non-linearity behavior of the sensor output data by compensation formulas is provided in the BME280 datasheet.


Add the code given below to derive the Celcius to Fahrenheit (°C to °F) conversion (°F = (°C × 9/5) + 32).

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

Add the code below 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);


Add the code given below to implement the change of sampling rate and prepare a message for the change on the serial terminal when you press 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");        


Add the code given below 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 *)&U6TXREG, 1, 1);

Set the CFORCE bit as '1' in the DMAC channel 0 configuration register, this is needed to work around a known erroneous behavior of UART with DMAC.
On PIC32MK devices, there is an errata for the UART. All the interrupts in UART are erroneously defined as edge sensitive instead of level sensitive. Hence, at the start, even if the buffer is empty, the interrupt is not generated. The interrupt is generated only after the buffer is filled once and then gets empty. Hence, to initiate the transfer for the first time, the application needs to force the transfer start. The subsequent transfers do not need to be forced as the transmit buffer goes from 1 character to 0 characters, that is, the transmit buffer becomes empty and that generates the interrupt.

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.