Getting Started with USB on SAM MCUs Using MPLAB® Harmony v3: Step 5

Last modified by Microchip on 2023/11/09 09:09

Add Application Code to the Project

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

  • common.h
  • app_eeprom.h
  • app_eeprom.c
  • app_sensor.h
  • app_sensor.c
  • app_usb.h
  • app_usb.c
  • main.c

They are available under <your unzipped folder path>\getting_started_drivers_middleware\dev_files

The application files app_eeprom.capp_sensor.c, and app_usb.c contain the application logic. They also contain placeholders that you will populate with the necessary code.

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

  • common.h
  • app_eeprom.h
  • app_eeprom.c
  • app_sensor.h
  • app_sensor.c
  • app_usb.h
  • app_usb.c
  • main.c
Copy Files

Figure 1: Copy Files

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

Paste and Replace Files

Figure 2: Paste and Replace Files

Back to Top


Add the application header file common.h to your project.

Add Existing Item…

Figure 3: Add Existing Item…

Select common.h

Figure 4: Select common.h

Added common.h

Figure 5: Added common.h

Back to Top


For a Harmony v3 application, MPLAB® Code Configurator (MCC) generates the application template files app_eeprom.happ_eeprom.capp_sensor.happ_sensor.capp_usb.happ_usb.cmain.c.

The main.c file calls the SYS_Tasks() routine which runs the Sensor, EEPROM, and USB application tasks. It also calls the requisite Driver, System Service, and Middleware tasks if they're added to the project using MCC.

SYS_Tasks Routine

Figure 6: SYS_Tasks Routine

Back to Top


The app_sensor.happ_eeprom.h, and app_usb.h files define the states of the application. Try to relate the states with the state diagrams shown in the beginning of this lab.

Sensor App States

Figure 7: Sensor App States

EEPROM App States

Figure 8: EEPROM App States

USB App States

Figure 9: USB App States

Back to Top


The app_sensor.happ_eeprom.h, and app_usb.h files also define the application data structures APP_SENSOR_DATAAPP_EEPROM_DATA, and APP_USB_DATA.

Sensor App Data

Figure 10: Sensor App Data

EEPROM App Data

Figure 11: EEPROM App Data

USB App Data

Figure 12: USB App Data

Back to Top


The functions APP_SENSOR_Tasks()APP_EEPROM_Tasks(), and APP_USB_Tasks() in the files app_sensor.happ_eeprom.h, and app_usb.h implement the application state machine.

Back to Top


The Sensor Task in app_sensor.c and the EEPROM Task in app_eeprom.c acts as the two Clients to the I²C Driver Instance 0.

I²C Driver Clients

Figure 13: I²C Driver Clients

Back to Top


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

Open the I²C Driver Instance 0 (which is associated with TWIHS0 Peripheral Library). 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.

Line numbers are provided beside each code snippet. If you don't see line numbers in MPLAB X IDE, enable them by navigating to View > Show Line Numbers from the menu bar.

You can use Ctrl + G to jump to a line number on the editor.

215 /* Open I2C Driver to interface with Sensor. */
216 app_sensorData.i2cHandle = DRV_I2C_Open( DRV_I2C_INDEX_0,
217      DRV_IO_INTENT_READWRITE );

Entered code is displayed

Set the transfer parameters for the sensor client after a valid handle to the driver is obtained. The transfer parameter sets the I²C clock speed to 100 kHz for this client.

223 /* Setup I2C transfer @ 100 kHz to interface with Sensor. */
224 DRV_I2C_TransferSetup(app_sensorData.i2cHandle,
225      &app_sensorData.i2cSetup);

Entered code is displayed

Register an event handler (callback) with the I²C driver for the sensor client. The event handler is called by the I²C driver when any request submitted by the sensor application client is completed.

227 /* Register I2C transfer complete Event Handler for Sensor. */
228 DRV_I2C_TransferEventHandlerSet(app_sensorData.i2cHandle
229         APP_SENSOR_I2CEventHandler, 0);

Entered code is displayed

Register a periodic callback with the TIME System Service for every 1000 milliseconds.

231 /* Register Timer Expiry Event Handler with
232  * Time System Service.
233  */

234 SYS_TIME_CallbackRegisterMS(APP_SENSOR_TimerEventHandler, 0,
235         (1000*APP_SENSOR_SAMPLING_RATE_IN_HZ),
236         SYS_TIME_PERIODIC);

Entered code is displayed

Set a flag in the periodic timer event handler. The sensor task will read the temperature when the flag is set and also toggle the I/O1 Xplained Pro LED.

110 /* Timer expired. */
111 app_sensorData.isTimerExpired = true;
112
113 /* Toggle LED*/
114 LED_Toggle();

Entered code is displayed

When the periodic timer expires, submit an I²C transfer request to read the temperature sensor value using the I²C driver write-then-read API.

259 /* Submit I2C transfer to read temperature sensor value. */
260 DRV_I2C_WriteReadTransferAdd( app_sensorData.i2cHandle,
261         APP_SENSOR_I2C_SLAVE_ADDR,
262         (void*)app_sensorData.i2cTxBuffer, 1,
263         (void*)app_sensorData.i2cRxBuffer, 2,
264         &app_sensorData.transferHandle );

Entered code is displayed

The I²C driver calls the sensor I²C event handler APP_SENSOR_I2CEventHandler() when a submitted request is complete and sets a flag indicating the read is complete.

89 /* I2C read complete. */
90 app_sensorData.isTemperatureReadComplete = true;

Entered code is displayed

The COMMON_APP_SENSOR_GetTemperature() function declared in common.h and defined in app_sensor.c provides a common interface between tasks and is used in app_usb.c to fetch the latest temperature value. Return the latest temperature value when this function is called.

171 /* Return latest temperature value */
172 return app_sensorData.temperature;

Entered code is displayed

Once the temperature is read, notify the EEPROM Task to log the current temperature value to EEPROM.

280 /* Notify the EEPROM task to log the temperature value to EEPROM. */
281 APP_EEPROM_SetTempWriteRequest(app_sensorData.temperature);

Entered code is displayed

Back to Top


Open app_eeprom.c and add the application code as shown in the following steps.

Associate the second I²C client, with the I²C Driver Instance 0. This is done by opening the I²C Driver Instance 0 again. The call to DRV_I2C_Open() API will now associate the EEPROM Client with the I²C Driver Instance 0. The returned handle will then be used by the EEPROM Task in all the subsequent calls to the driver.

271 /* Open I2C Driver to interface with EEPROM. */
272 app_eepromData.i2cHandle = DRV_I2C_Open( DRV_I2C_INDEX_0,
273          DRV_IO_INTENT_READWRITE );

Entered code is displayed

Like the Sensor Client, set up the transfer parameters for the EEPROM Client after a valid handle to the driver is obtained. The transfer parameters set the I²C clock speed to 400 kHz for this client.

279 /* Setup I2C transfer @ 400 kHz to interface with EEPROM. */
280 DRV_I2C_TransferSetup(app_eepromData.i2cHandle,
281         &app_eepromData.i2cSetup);

Entered code is displayed

  • The call to DRV_I2C_TransferSetup() overrides the baud rate set in the I²C driver configuration using MCC.
  • I²C was configured to run at 400 kHz using MCC. While in the application, the Sensor Task has reconfigured it to run at 100 kHz and the EEPROM Task configured it to run at 400 kHz. This illustrates how the Harmony I²C driver handles the peripheral's configuration depending on the client accessing the peripheral.

Like the Sensor Client, register an event handler (callback) with the I²C driver for the EEPROM Client. The event handler would be called by the I²C driver when any request submitted by the EEPROM application client is completed.

283 /* Register I2C transfer complete Event Handler for EEPROM. */
284 DRV_I2C_TransferEventHandlerSet(app_eepromData.i2cHandle,
285         APP_EEPROM_I2CEventHandler, 0);

Entered code is displayed

When the Sensor Task notification occurs, submit an I²C transfer request to write the temperature sensor value using the I²C driver write API.

Entered code is displayed

The COMMON_APP_EEPROM_GetTemperature() function declared in common.h and defined in app_eeprom.c provides a common interface between tasks and is used in app_usb.c to fetch the last five temperature values from the EEPROM. When this is called, submit an I²C transfer request to read the last five temperature values from the EEPROM using the I²C driver write-then-read API. Once this completes, store the temperature values in the requisite buffer.

199 /* Submit I2C transfer to store the temperature value in EEPROM */
200 DRV_I2C_WriteTransferAdd(app_eepromData.i2cHandle,
201         APP_EEPROM_I2C_SLAVE_ADDR,
202         (void*)app_eepromData.i2cTxBuffer, 2,
203         &app_eepromData.transferHandle);

Entered code is displayed

The I²C driver calls the EEPROM I²C event handler APP_EEPROM_I2CEventHandler() when a submitted request is complete and sets a flag indicating the read is complete.

87 /* I2C read complete. */
88 app_eepromData.reqStatus = APP_EEPROM_REQ_STATUS_DONE;

Entered code is displayed

Back to Top


Open app_usb.c and add the application code as shown in the following steps.

Open the USB device layer and store the handle returned, this handle will be used for all subsequent calls to the device layer.

388 /* Open the device layer */
389 app_usbData.deviceHandle = USB_DEVICE_Open(USB_DEVICE_INDEX_0,
390         DRV_IO_INTENT_READWRITE);

Entered code is displayed

Register a callback with the device layer using the handle returned in the previous step to receive the USB device layer's event notifications.

394 /* Register a callback with device layer to get event 
395 * notification (for end point 0) 
396 */

397 USB_DEVICE_EventHandlerSet(app_usbData.deviceHandle,
398        APP_USB_USBDeviceEventHandler, 0);

Entered code is displayed

Once the USB device is plugged in and the enumeration is complete, the previously configured callback will be called by the USB device layer. Set a flag to notify the state machine that the USB device is now configured and turn on the SAME70 Xplained board LED to show the same.

231 /* Mark that the device is now configured */
232 app_usbData.isConfigured = true;
233  
234 /* Update LED to show configured state */
235 USB_LED_Clear();

Entered code is displayed

The LED on the SAM E70 Xplained Pro is active low, so, clear the pin to turn it on.

Since the USB device is now configured, you can now register a separate callback to receive the CDC function driver's events as shown in the accompanying example.

237  /* Register the CDC Device application event handler here.
238  * Note how the appData object pointer is passed as the
239  * user data 
240  */

241 USB_DEVICE_CDC_EventHandlerSet(USB_DEVICE_CDC_INDEX_0,
242         APP_USB_USBDeviceCDCEventHandler,
243         (uintptr_t)&app_usbData);

Entered code is displayed

Once the USB device is configured, schedule a read using the USB CDC function driver's USB_DEVICE_CDC_Read() API.

442 /* Schedule read */
443 USB_DEVICE_CDC_Read (USB_DEVICE_CDC_INDEX_0,
444         &app_usbData.readTransferHandle,
445         app_usbData.cdcReadBuffer,
446         APP_USB_READ_BUFFER_SIZE);

Entered code is displayed

When data is successfully received by the CDC function driver from the USB host (PC), the APP_USB_USBDeviceCDCEventHandler() is called. Using the USB_DEVICE_CDC_EVENT_READ_COMPLETE event, notify the state machine by setting a flag as shown in the accompanying example.

150 /* Notify state machine that a read was completed */
151 appDataObject->isReadComplete = true;

Entered code is displayed

Process the character read from the host, if it's 1, toggle the board LED.

511 /*Toggle the E70 Xplained board LED*/
512 USB_LED_Toggle();

Entered code is displayed

Process the character read from the host, if it's 2, show the latest temperature value by using the common interface to the Sensor Task.

521 /* Fetch the latest temperature from the sensor */
522 temp = COMMON_APP_SENSOR_GetTemperature();

Entered code is displayed

Process the character read from the host, if it's 3, show the last five temperature values by reading it from the EEPROM using the common interface to the EEPROM Task.

544 /* Fetch last 5 temperature values from the EEPROM */
545 bool result = COMMON_APP_EEPROM_GetTemperature(temps, 5);

Entered code is displayed

Process the character read from the host, if it's 4, show the latest light sensor value by reading the sensor voltage using the AFEC1 Peripheral Library. The output is formatted as a percentage of the light sensor's full-scale output voltage.

581 /* Read the ADC result */
582 adc_count = AFEC1_ChannelResultGet(AFEC_CH6);

Entered code is displayed

Once a valid command is processed, schedule a write for the data to be displayed using the USB CDC function driver's USB_DEVICE_CDC_Write API.

613 /* Schedule write */
614 USB_DEVICE_CDC_Write(USB_DEVICE_CDC_INDEX_0,
615         &app_usbData.writeTransferHandle,
616         app_usbData.cdcWriteBuffer,
617         app_usbData.numBytesWrite,
618         USB_DEVICE_CDC_TRANSFER_FLAGS_DATA_COMPLETE);

Entered code is displayed

When data is successfully sent by the CDC function driver to the USB host (PC), the APP_USB_USBDeviceCDCEventHandler() is called. Using the USB_DEVICE_CDC_EVENT_WRITE_COMPLETE event, notify the state machine by setting a flag as shown in the accompanying example.

183 /* Notify state machine that a write was completed */
184 appDataObject->isWriteComplete = true;

Entered code is displayed

You are now ready to build the code!

Back to Top