Lab 1: Create the Application

Step 10: Add Application Code

The application is already developed and is available in the following files.

  • app_sensor.c
  • app_sensor.h

They are available under <your unzipped folder path>\getting_started_pic32_wfi32e\Lab1\dev_files.

The app_sensor.c application file contains the application logic. It also contains placeholders that you will populate with the necessary code.


Go to the <your unzipped folder path>\getting_started_pic32_wfi32e\Lab1\dev_files folder and copy the pre-developed files:

  • app_sensor.c
  • app_sensor.h

Paste and replace (overwrite) the files of your project at <your harmony 3 project folder path>\PIC32_WFI32E\firmware\src with the copied files.


Open app_sensor.c in MPLAB X and add the application code as shown in the following steps.

Tip: Search for the string Step # in the target file to locate the position where you are supposed to write the code.

Note: The code snippets have not been added to the application. You need to write them according to the lab recommendations to understand how to use the drivers, system services, peripheral libraries, registers, handle callbacks and understand the overall application flow.


Open the I²C Driver Instance 0. The call to DRV_I2C_Open() Application Programming Interface (API) will associate the Sensor Client with the I²C Driver Instance 0. The returned handle will be used by the Sensor Task in all the subsequent calls to the driver.

Search for Step #1:

  • To open the I2C driver, you will use the following function:

  • drvIndex is the instance of the I2C driver. Here you will use the first instance of I2C (I2C1), which corresponds to the index 0. So this argument should be populated with DRV_I2C_INDEX_0.
  • ioIntent is the mode to open the I2C driver. It could be read, write, or many other modes. For the application, you will use the read and write mode with DRV_IO_INTENT_READWRITE.
  • DRV_HANDLE is the returned handle that you must use each time you want to call an I2C driver function with this instance. This return value needs to be stored in appSensorData.i2c.instance.


Register an event handler (callback) with the I2C Driver. The event handler is called by the I2C Driver when the temperature sensor read request is completed.

Search for Step #2:

  • You will use the following function to register an event handler callback.

  • handle is the current instance of the I2C driver handle. It was populated during step #1 and should be stored in appSensorData.i2c.instance.
  • eventHandler is a function pointer to the target callback. Here you will use the function APP_I2C_EventHandler, which is already declared.
  • context allows sending custom arguments to the callback. Here it is not used. So simply write a 0.


It is now time to fill the I2C event handler with the code to execute each time an I2C read transaction is completed.

Search for Step #3:
In an interrupt-driven application, it is a good practice to execute the least code possible inside the interrupt. In the I2C event handler, only the state of the state machine will be changed.
Before that, it is also necessary to check which event has triggered the callback. The event argument can take several values allowing several error levels but here you will use DRV_I2C_TRANSFER_EVENT_COMPLETE and DRV_I2C_TRANSFER_EVENT_ERROR.

  • Use the variable appSensorData.state to change the direction of the state machine.
  • Jump to APP_SENSOR_STATE_USB_PRINT if the transfer is completed.
  • Jump to APP_SENSOR_STATE_ERROR in case of a transfer error.


Before reading or writing data on the I2C bus, it is required to specify the address of the I2C sensor and specify the addresses of the internal registers.
To find them, open the temperature sensor datasheet located into <your unzipped folder path>\getting_started_pic32_wfi32e\Lab1\datasheet.

Search for Step #4a

  • Table 5.1 of the datasheet describes how to build the address of the sensor.
Figure 1: Temperature Sensor Address Byte
  • You can ignore the Bit 0 as the read and write bit setup is handled by the I2C Driver.
  • The 7-bit address must be sent to the API of the I2C Driver.
  • Bits 4 to 7 are the base address of the sensor. To address the temperature sensor, the base address should be 0x9.
  • Then, bits 1 to 3 are related to the hardware configuration of the pins A0, A1, and A2. Flip the IO1 Xplained Pro board to check the configuration of your board. If the pad is grounded, it corresponds to 0 for the bit in the address. Otherwise, the pin is connected to a pull-up and it corresponds to 1 for the bit in the address.
  • Open app_sensor.h and if required, adjust the definitions of the pins according to your setup.

Back to app_sensor.c, Search for Step #4a and build the address of your sensor using binary masks (operator « and |) and the constants defined above. Store the address of the sensor in global variable appSensorData.i2c.sensor.address.

Search for Step #4b:

  • Table 6.1 of the datasheet describes the register address for read or read/write operations from the host.
Figure 2: Temperature Sensor Registers
  • For this application, you need to read the temperature register and also configure the precision of the temperature sensor read.
  • The addresses of the Temperature register and Configuration register have been already defined in app_sensor.h.

In app_sensor.c, Search for Step #4b and use the following global variables to store the pre-defined addresses of the registers.


To read the temperature sensor value, you will populate the following custom function:

Search for Step #5a:

  • The I2C frame structure is completely handled by the I2C driver.
  • To read a register of the sensor, you will use the following API:

  • handle is the current I2C driver instance. You will use appSensorData.i2c.instance.
  • address is the sensor address. You will use appSensorData.i2c.sensor.address.
  • buffer is the address of the buffer where the read data will be stored. You will use appSensorData.i2c.i2cRxBuffer, which is defined as an array of three uint8_t elements.
  • size is the number of bytes to read. You will use 2 to read 16-bits of the Temperature register.
  • transferHandle is required to store the status of the transfer. You will use appSensorData.i2c.i2cTransferHandle.

Search for Step #5b:

  • To retrieve the error code and pass it to the application, use the function DRV_I2C_ErrorGet():


The custom I2C driver is now ready to operate and you will use the Curiosity board switch (SW1) to trigger the temperature sensor read. The switch is connected to pin RA10 of the WFI32E module. The GPIO pin has been already configured in Harmony Pin Manager. In the app_sensor.c application code, you should register the interrupt callback.

Search for Step #6a:

  • You will use the following GPIO PLIB function to register the callback:

  • The GPIO_PinInterruptCallbackRegister function 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.
  • Use the following arguments to fill the function:
    • pin - GPIO_PIN_RA10.
    • callback - APP_SWITCH_EventHandler
    • context - 0

Search for Step #6b:

  • Enable the interrupt with the function GPIO_PinInterruptEnable().


Search for Step #7:

  • The button SW1 is used to toggle the application.
  • In the APP_SWITCH_EventHandler callback, add the code to toggle the flag.
    • appSensorData.isLogEnable = true to run the application.
    • appSensorData.isLogEnable = false to halt the application.


Search for Step #8:

  • You will register a callback to interrupt periodically every 10 seconds using SYS_TIME API.

  • Use the following arguments to fill the function:
    • callback - SYS_TIME_EventHandler
    • context - (uintptr_t)NULL
    • ms - 10000
    • type - SYS_TIME_PERIODIC


Search for Step #9:

  • Set the flag appSensorData.tmrExpired to notify the application that the timer raised an interrupt.


It is time to populate the state machine.

Search for Step #10a:

  • In the APP_SENSOR_STATE_IDLE state, add the following snippet of code to trigger the temperature read state once the application is enabled and the timer is expired.

Search for Step #10b:

  • In the APP_SENSOR_STATE_I2C_READ state, make a call to the function APP_I2C_TempRead() and test the returned error code value.
    • DRV_I2C_ERROR_NONE: switch back to IDLE state waiting for the I2C driver to finish the transfer task.
    • else: switch to ERROR state.

Search for Step #10c:

  • In the APP_SENSOR_STATE_USB_PRINT state, add the following code to convert binary data to an understandable format and print the value on the serial console over the USB CDC.
  • Here let's assume the temperature is always positive and the temperature sensor is configured with default 9-bit precision.
  • From Table 6-3 of the temperature sensor datasheet, with 9-bits precision binary, a value can go from 0 to 2^8 = 256.
Figure 3: Temperature Sensor Registers
  • But the full scale of the temperature sensor is +125 °C. To convert the binary value to a °C value, it must be multiplied by 0.5.
  • Add the following snippet of code to perform the following operations:
    • Convert and store the temperature value
    • Print the data on the serial console with SYS_DEBUG_PRINT(SYS_ERROR_DEBUG, <data>).
    • Switch to IDLE state.

Search for Step #10d:

  • In the APP_SENSOR_STATE_ERROR state, implement your error handler by turning on the Red LED with BSP functions.

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.