Getting Started with USB on SAM MCUs Using MPLAB® Harmony v3: Step 5
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.c, app_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
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.
Add the application header file common.h to your project.
For a Harmony v3 application, MPLAB® Code Configurator (MCC) generates the application template files app_eeprom.h, app_eeprom.c, app_sensor.h, app_sensor.c, app_usb.h, app_usb.c, main.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.
The app_sensor.h, app_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.
The app_sensor.h, app_eeprom.h, and app_usb.h files also define the application data structures APP_SENSOR_DATA, APP_EEPROM_DATA, and APP_USB_DATA.
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.
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.
216 app_sensorData.i2cHandle = DRV_I2C_Open( DRV_I2C_INDEX_0,
217 DRV_IO_INTENT_READWRITE );
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.
224 DRV_I2C_TransferSetup(app_sensorData.i2cHandle,
225 &app_sensorData.i2cSetup);
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.
228 DRV_I2C_TransferEventHandlerSet(app_sensorData.i2cHandle,
229 APP_SENSOR_I2CEventHandler, 0);
Register a periodic callback with the TIME System Service for every 1000 milliseconds.
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);
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.
111 app_sensorData.isTimerExpired = true;
112
113 /* Toggle LED*/
114 LED_Toggle();
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.
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 );
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.
90 app_sensorData.isTemperatureReadComplete = true;
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.
172 return app_sensorData.temperature;
Once the temperature is read, notify the EEPROM Task to log the current temperature value to EEPROM.
281 APP_EEPROM_SetTempWriteRequest(app_sensorData.temperature);
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.
272 app_eepromData.i2cHandle = DRV_I2C_Open( DRV_I2C_INDEX_0,
273 DRV_IO_INTENT_READWRITE );
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.
280 DRV_I2C_TransferSetup(app_eepromData.i2cHandle,
281 &app_eepromData.i2cSetup);
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.
284 DRV_I2C_TransferEventHandlerSet(app_eepromData.i2cHandle,
285 APP_EEPROM_I2CEventHandler, 0);
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.
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.
200 DRV_I2C_WriteTransferAdd(app_eepromData.i2cHandle,
201 APP_EEPROM_I2C_SLAVE_ADDR,
202 (void*)app_eepromData.i2cTxBuffer, 2,
203 &app_eepromData.transferHandle);
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.
88 app_eepromData.reqStatus = APP_EEPROM_REQ_STATUS_DONE;
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.
389 app_usbData.deviceHandle = USB_DEVICE_Open(USB_DEVICE_INDEX_0,
390 DRV_IO_INTENT_READWRITE);
Register a callback with the device layer using the handle returned in the previous step to receive the USB device layer's event notifications.
395 * notification (for end point 0)
396 */
397 USB_DEVICE_EventHandlerSet(app_usbData.deviceHandle,
398 APP_USB_USBDeviceEventHandler, 0);
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.
232 app_usbData.isConfigured = true;
233
234 /* Update LED to show configured state */
235 USB_LED_Clear();
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.
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);
Once the USB device is configured, schedule a read using the USB CDC function driver's USB_DEVICE_CDC_Read() API.
443 USB_DEVICE_CDC_Read (USB_DEVICE_CDC_INDEX_0,
444 &app_usbData.readTransferHandle,
445 app_usbData.cdcReadBuffer,
446 APP_USB_READ_BUFFER_SIZE);
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.
151 appDataObject->isReadComplete = true;
Process the character read from the host, if it's 1, toggle the board LED.
512 USB_LED_Toggle();
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.
522 temp = COMMON_APP_SENSOR_GetTemperature();
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.
545 bool result = COMMON_APP_EEPROM_GetTemperature(temps, 5);
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.
582 adc_count = AFEC1_ChannelResultGet(AFEC_CH6);
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.
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);
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.
184 appDataObject->isWriteComplete = true;
You are now ready to build the code!