Logo
CapTIvate™ Technology Guide  v1.10.00.00

Purchase the kit now
Go to ti.com/CapTIvate

Software Library

Introduction

The CapTIvate™ Software Library is a collection of target software components designed to help shorten the development process when working with CapTIvate™ MCUs. The library is provided and supported by Texas Instruments and is delivered with the CapTIvate™ Design Center.

The library provides the following features:

  1. Hardware abstraction of the CapTIvate peripheral features
  2. Processing of button, slider, wheel, and proximity sensors with simple callback reporting when measurement and processing are complete
  3. User interface management to enable a simple top level API that is easy to use
  4. Electromagnetic compatibility features for improving noise immunity
  5. Communications infrastructure for connecting a CapTIvate™ MCU to the CapTIvate™ Design Center during tuning or to a host processor in an application

These features provide the following main benefits:

  1. Simplification of sensor configuration, measurement, processing and data communication
  2. Faster application development cycles
  3. Seamless integration with the CapTIvate™ Design Center development GUI
  4. Reduced code footprint on devices with CapTIvate™ software in ROM

The library was designed and organized for capacitive user interface applications. However, it may also be used for other applications that require the ability to measure relative changes in capacitance.

Using This Chapter

This chapter consists of the following main sections:

  1. The Overview section introduces the programming model, organization, and architecture of the library.
  2. The Getting Started section introduces how to get up and running with the starter project, as well as how to add CapTIvate™ to an existing software project.
  3. The How-To section provides basic code snippets that demonstrate how to do basic things.
  4. The Technical Details section discusses advanced software implementation details.
  5. The Base Module, Advanced Module, and Communications Module sections each provide a detailed description of the respective software module for advanced users.

The Getting Started and How-To are the most helpful sections for new users that want to quickly begin developing applications.

Device and Tools Support

The CapTIvate™ Software Library can only be used with MSP devices that have CapTIvate™ technology.

Supported Devices

  • MSP430FR26xx devices with CapTIvate™ technology
  • MSP430FR25xx devices with CapTIvate™ technology

Supported Tool Chains

The following development tool chains are supported.

  • TI Code Composer Studio
    • IDE version 6.1.0 or higher
    • MSP430 Emulator Tools 6.2.1.0 or higher
    • EABI (ELF) output format
  • IAR Embedded Workbench
    • IDE version 6.3 or higher
    • MSP430 Emulator Tools 6.2.1.0 or higher

Programming Language

The software library is available in C. It follows C99 conventions and uses C99 primitives (uintX).

Delivery Mechanism

The CapTIvate™ Software Library and CapTIvate™ Design Center GUI have linked functionality. Features that exist in the software library are configurable via the Design Center, and data measured via the software library can be communicated back to the Design Center. Because of this, the software library and Design Center are always released together as one software download and installation. The Design Center is the sole point of access to the software library.

Change Control

Although they are delivered together, the CapTIvate Software Library has its own version tracking and change control. Every major library release comes with change control data in the Software Library API guide that describes any new features that have been added and any changes to existing functionality.


Overview

This section introduces the CapTIvate™ Software Library programming model, its organization and its architecture. It also discusses delivery of the library and version control.

Programming Model

The CapTIvate™ Software Library consists of several software modules and sub-modules that work together to provide various features and abstract complexity. The software library model will be introduced here in a "top-down" approach, starting from the highest point of abstraction and working downward to the lowest point.

Objects

The software library function calls operate on C structures which will be referred to in this section as objects. All of the main objects (C type definitions) for the software library are defined in the BASE module inside of the CAPT_Type.h header file. See the type definitions section for more details.

Generic Capacitive Touch Application

Capacitive sensing applications involve the continual measurement and post-processing of one or more capacitive sensors. As such, an application typically has the following flow:

Initialize MCU
Initialize user interface
Calibrate user interface
Loop(Forever)
If (Time to update = true)
Then
Update user interface
Report user interface status
End If

Top Level Object (User Interface Application)

The CapTIvate™ Software Library utilizes this basic application model as the framework for the top level API and top level object. The top level object in the software library is the user interface application, or tCaptivateApplication. Functions are provided for initializing a user interface, calibrating a user interface, and updating a user interface. These functions are implemented as CAPT_initUI(), CAPT_calibrateUI(), and CAPT_updateUI(), respectively. An application can be created just by using these three functions. This is discussed in the Use the Top Level API section.

In order to realize the top level API, the top level object (tCaptivateApplication) holds information about the state of the user interface, how many sensors there are to update, where to find those sensors, and how often to update them.

Sensor Object

It then follows that the next object that is needed is a sensor object, or tSensor. A sensor object is an abstracted user interface control. The software library supports button group, slider, wheel, and proximity sensor types. Every sensor object contains the following information:

  1. Information about which electrodes to measure and how to measure them in parallel (element objects and time cycle objects)
  2. Information about how to configure the CapTIvate™ technology peripheral for measurement (conversion control parameters)
  3. Information about how to interpret the data from the measurement (tuning parameters)

Various functions can operate on sensor objects directly. For example, it is possible to only update a particular sensor via a call to CAPT_updateSensor() or CAPT_updateSensorWithEMC(). The top level API function CAPT_updateUI() merely calls CAPT_updateSensor() or CAPT_updateSensorWithEMC() for each sensor in the UI application.

Time Cycle Object

The sensor object links to time cycle objects. A time cycle object is defined as tCycle. A time cycle is nothing more than a group of element objects that may be measured in parallel.

Element Object

Element objects are the lowest abstraction level, and can be thought of as the software representation of a single electrode, whether it is self or mutual capacitance. Each element contains the following types of information:

  1. Information about the pin(s) the electrode is connected to
  2. Any tuning parameters that are specific to the element (such as a touch threshold)
  3. Any data associated with the element (such as it's current sample or long term average)
  4. Any status flags that are specific to the element (such as touch or proximity status).

Object Tree

As an example, a basic application with one sensor and 4 elements organized into two time cycles could be represented with the object tree diagram shown below.

lib_objecttree_basic.png
Example Application Object Tree

In software, this configuration would have the following structure:

Element Definitions

// Sensor: keypad, Element: E00
uint16_t keypad_E00_RawCnts[CAPT_SELF_FREQ_CNT];
tCaptivateElementTuning keypad_E00_Tuning[CAPT_SELF_FREQ_CNT];
tElement keypad_E00 =
{
.ui8RxPin = 0,
.ui8RxBlock = 0,
.ui8TouchThreshold = 10,
.pRawCount = keypad_E00_RawCnts,
.pTuning = keypad_E00_Tuning,
};
// Sensor: keypad, Element: E01
uint16_t keypad_E01_RawCnts[CAPT_SELF_FREQ_CNT];
tCaptivateElementTuning keypad_E01_Tuning[CAPT_SELF_FREQ_CNT];
tElement keypad_E01 =
{
.ui8RxPin = 0,
.ui8RxBlock = 1,
.ui8TouchThreshold = 10,
.pRawCount = keypad_E01_RawCnts,
.pTuning = keypad_E01_Tuning,
};
// Sensor: keypad, Element: E02
uint16_t keypad_E02_RawCnts[CAPT_SELF_FREQ_CNT];
tCaptivateElementTuning keypad_E02_Tuning[CAPT_SELF_FREQ_CNT];
tElement keypad_E02 =
{
.ui8RxPin = 1,
.ui8RxBlock = 0,
.ui8TouchThreshold = 10,
.pRawCount = keypad_E02_RawCnts,
.pTuning = keypad_E02_Tuning,
};
// Sensor: keypad, Element: E03
uint16_t keypad_E03_RawCnts[CAPT_SELF_FREQ_CNT];
tCaptivateElementTuning keypad_E03_Tuning[CAPT_SELF_FREQ_CNT];
tElement keypad_E03 =
{
.ui8RxPin = 1,
.ui8RxBlock = 1,
.ui8TouchThreshold = 10,
.pRawCount = keypad_E03_RawCnts,
.pTuning = keypad_E03_Tuning,
};

Note that each element has three components:

  1. An array for storing raw data after a measurement is complete (keypad_E0x_RawCnts[])
  2. An array for storing element tuning values (keypad_E0x_Tuning[])
  3. The element data object itself (keypad_E0x)

The first component is the raw data array. Whenever an element is updated, the raw conversion results are populated in this array. Normally, the raw data array is an array of one value (keypad_E0x_RawCnts[1]). However, if noise immunity is enabled (EMC features), the raw data array may be larger to store conversion results from a multi-frequency conversion.

The second component is the element's tuning. Each element is calibrated with specific coarse gain, fine gain, and offset subtraction values. To understand what these parameters effect, check out the CapTIvate peripheral section of the technology chapter. Just like the raw data array, the tuning is stored in an array as well. If multi-frequency scanning is used to support noise immunity, a tuning is stored for each conversion frequency.

The final component is the element data object (tElement). This object stores the pin definition for the element. An electrode on CAPx.y would be mapped in this way, where 'x' is the CapTIvate™ measurement block the electrode is connected to, and 'y' is the pin on that block.

.ui8RxPin = y,
.ui8RxBlock = x,

In addition to the pin connection information, the element object also stores the touch threshold for this element. This specifies the level of interaction required to trigger a touch detection. Each element has its own touch threshold.

Finally, the element object is linked to the raw data and tuning arrays via pointers.

Time Cycle Definitions

// Time Cycle: keypad_C00
tElement* keypad_C00_Elements[2] =
{
&keypad_E00,
&keypad_E01,
};
tCycle keypad_C00 =
{
.ui8NrOfElements = 2,
.pElements = keypad_C00_Elements,
};
// Time Cycle: keypad_C01
tElement* keypad_C01_Elements[2] =
{
&keypad_E02,
&keypad_E03,
};
tCycle keypad_C01 =
{
.ui8NrOfElements = 2,
.pElements = keypad_C01_Elements,
};

As discussed previously, time cycles are simply a collection of element objects that have the capability of being measured in parallel. Each time cycle is composed of two components:

  1. An array of pointers to the elements in the cycle (keypad_C0x_Elements[])
  2. The cycle object itself (keypad_C0x)

The array of element pointers provides the link to the element objects that belong to the cycle. The cycle object links to that array, and also defines how many elements are in the cycle.

Sensor Definition

//Sensor: keypad
const tCycle* keypad_Cycles[2] =
{
&keypad_C00,
&keypad_C01,
};
tButtonSensorParams keypad_Params;
tSensor keypad =
{
// Basic Properties
.TypeOfSensor = eButtonGroup,
.SensingMethod = eSelf,
.DirectionOfInterest = eDOIDown,
.pvCallback = NULL,
.ui8NrOfCycles = 2,
.pCycle = keypad_Cycles,
.pSensorParams = (tGenericSensorParams*)&keypad_Params,
// Conversion Control Parameters
.ui16ConversionCount = 500,
.ui16ConversionGain = 200,
.ui8FreqDiv = 2,
.ui8ChargeLength = 0,
.ui8TransferLength = 0,
.bModEnable = false,
.ui8BiasControl = 3,
.bCsDischarge = true,
.bLpmControl = false,
.ui8InputSyncControl = 0,
.bTimerSyncControl = false,
.bIdleState = true,
// Tuning Parameters
.ui16ProxThreshold = 10,
.ui16NegativeTouchThreshold = 20,
.ui16ErrorThreshold = 8191,
.ui16TimeoutThreshold = 1000,
.ProxDbThreshold.DbIn = 1,
.ProxDbThreshold.DbOut = 0,
.TouchDbThreshold.DbIn = 1,
.TouchDbThreshold.DbOut = 0,
.bCountFilterEnable = true,
.ui8CntBeta = 1,
.bSensorHalt = false,
.bPTSensorHalt = true,
.bPTElementHalt = true,
.ui8LTABeta = 7,
.bReCalibrateEnable = true,
};

The sensor definition has three components:

  1. An array of pointers to the cycles in the sensor (keypad_Cycles[])
  2. The sensor type specific parameters (in this case, a button group) (keypad_Params)
  3. The generic sensor object itself (keypad)

The pointer to cycle array allows the sensor to find its child objects (cycles, and through the cycles, the elements). The sensor type specific parameters component stores parameters that are specific to a sensor type. For example, a button group, slider/wheel, and proximity sensor all have different parameter structures.

The remainder of the parameters in the sensor object provide the conversion control and tuning configuration, as set up in the CapTIvate™ Design Center.

UI Application Definition

// Application
tSensor* g_pCaptivateSensorArray[CAPT_SENSOR_COUNT] =
{
&keypad,
};
tCaptivateApplication g_uiApp =
{
.state = eUIActive,
.pSensorList = &g_pCaptivateSensorArray[0],
.ui8NrOfSensors = CAPT_SENSOR_COUNT,
.ui8AppLPM = LPM0_bits,
.bElementDataTxEnable = true,
.bSensorDataTxEnable = true,
.ui16ActiveModeScanPeriod = 33,
.ui16WakeOnProxModeScanPeriod = 100,
.ui16InactivityTimeout = 32,
.ui8WakeupInterval = 5,
};

The application definition has two components:

  1. An array of pointers to the sensors in the application (g_pCaptivateSensorArray[])
  2. The application object itself (g_uiApp)

The array of pointers to sensors allows the top level API to find all of the sensors in the application through the application structure. Note that the application structure also defines the following items:

  1. The low power mode to use during conversions (.ui8AppLPM)
  2. Element and sensor data transmission enable/disable (.bElementDataTxEnable, .bSensorDataTxEnable)
  3. The scan periods to use in active mode (.ui16ActiveModeScanPeriod)
  4. A place holder for wake-on-proximity parameters (.ui16WakeOnProxModeScanPeriod, .ui16InactivityTimeout, .ui8WakeupInterval)

Accessing Measurement Results and Data

In general, the software library operates on the principle of refreshing data inside of objects, rather than returning results directly via a function call. For example, when a top level API call is made to a function like CAPT_updateUI(), all sensors in the UI and each of those sensor's child elements will have their data structures refreshed. CAPT_updateUI() does not provide any status information directly when returning to the application.

As such, it is the responsibility of the application to directly access the results of a measurement in the appropriate object data structure. A callback function may be registered with any sensor, that will be called whenever a sensor's data is refreshed.

Organization and Architecture

The CapTIvate™ Software Library is organized into three major modules by functionality: BASE, ADVANCED, and COMM. Each module has several sub-modules, or "layers". Some of those sub-modules are delivered as source code; others are delivered as object code.

BASE Module

The BASE module is the core of the software library. It contains the hardware abstraction layer, the touch layer, the interrupt service routine (ISR), and the type definitions for the library. The touch layer acts as a "hub," providing functions for calibrating, measuring, and processing sensors. For a detailed overview of the BASE module, see the Base Module section.

ADVANCED Module

The ADVANCED module provides several processing plug-ins to the BASE module. This includes button processing, slider and wheel processing, and EMC processing for noise immunity. It also contains the manager layer, which serves as the top level API for the library.

The basic software stack is shown below.

lib_architecture_nocomm.png
CapTIvate™ Software Library Organization (Without COMMs)

As shown here, from the application space it is only necessary to call the top level API functions in the manager layer to have a functioning application. The touch layer handles pulling in the necessary plug-ins (button, slider/wheel, EMC), so there is no need to call these functions from the application space. For a detailed overview of the ADVANCED module, see the Advanced Module section.

User Configuration

Every CapTIvate™ application has a user configuration that defines all of the objects on the application. This includes the application, sensor, cycle, and element object definitions. This configuration is typically auto-generated by the CapTIvate™ Design Center.

COMM Module

The COMM module provides communication services to either a host processor or the CapTIvate Design Center. It contains a top level interface layer, a protocol layer, serial drivers, and several data structures. The expanded software stack with the communications module is shown below. For a detailed overview of the COMM module, see the Communications Module section.

lib_architecture.png
CapTIvate™ Software Library Organization (With COMMs)

Source Code Directory Structure

The library source code is organized into sub-directories by module (BASE, ADVANCED, or COMM). In addition to the HAL, Touch, and ISR components, the BASE directory contains the following files:

  • captivate.lib This is the CCS library archive. It contains all of the functions that are pre-compiled and delivered as object code for linking against the CCS compiler.
  • captivate.r43 This is the IAR library archive. It contains all of the functions that are pre-compiled and delivered as object code for linking against the IAR compiler.
  • lnk_captivate.cmd This linker command file tells the CCS linker about the CapTIvate peripheral address space.
  • rom_captivate.h This is the ROM function header file. It defines the ROM function calls based on the ROM function table.
  • rom_map_captivate.h This is the ROM map header file. It controls which ROM functions are valid for the version of the library that is being compiled. When making ROM calls, it is best to use the MAP_* convention. See the ROM function overview for details on how to call ROM functions.

Getting Started

Starting from Scratch with the Starter Project

The recommended way to get started with a CapTIvate™ software library project is to generate a new starter software project with the CapTIvate™ Design Center. To learn how to do this, step through the new sensor project design workshop.

This section will focus on the features of the starter project itself. The software library starter projected is a ready-to-go application that includes the following software components:

  1. The CapTIvate™ Software Library
  2. The MSP Peripheral Driver Library (DriverLib), delivered as pre-compiled object code
  3. A board support package, configured for the MSP-CAPT-FR2633 EVM
  4. An example application with wake-on-proximity support built-in
  5. An example main.c

The starter project contains everything that is needed to bring up the MCU, calibrate and run a capacitive sensing application per the specified user configuration, and communicate measurement data. It serves as a known-good starting point for new development and tuning. Once an application is tuned, features can be added and removed from the starter application to build toward a final production application.

Directory Structure

The directory structure of the starter project is shown below in CCS:

starter_project_files.png
Starter Project Files

The captivate directory contains the CapTIvate™ Software Library. This directory will likely not need to be edited during development.

The captivate_app directory contains the board support package (CAPT_BSP) and the example wake-on-proximity application (CAPT_APP). This directory contains starter files that should be modified to suite the needs of each individual application.

The captivate_config directory contains the automatically generated user configuration files that describe the capacitive sensing application as specified in the CapTIvate™ Design Center. The CAPT_UserConfig.c and CAPT_UserConfig.h files in this directory should never be edited manually, as changes made to these files are overwritten if the Design Center is utilized to update the configuration. For this reason, it is best to modify all configuration parameters from inside the Design Center.

The driverlib directory contains the MSP430 peripheral driver library. To lower compilation times, it is delivered as a pre-compiled library archive. It is possible to replace this DriverLib directory with the standard, open-source driver library, if desired.

Main

The starter application main.c is shown below:

#include <msp430.h> // Generic MSP430 Device Include
#include "driverlib.h" // MSPWare Driver Library
#include "captivate.h" // CapTIvate Touch Software Library
#include "CAPT_App.h" // CapTIvate Application Code
#include "CAPT_BSP.h" // CapTIvate EVM Board Support Package
void main(void)
{
//
// Initialize the MCU
// BSP_configureMCU() sets up the device IO and clocking
// The global interrupt enable is set to allow peripherals
// to wake the MCU.
//
WDT_A_hold(WDT_A_BASE);
BSP_configureMCU();
__bis_SR_register(GIE);
//
// Start the CapTIvate application
//
CAPT_appStart();
//
// Background Loop
//
while(1)
{
//
// Run the captivate application handler.
// Set LED1 while the app handler is running,
// and set LED2 if proximity is detected
// on any sensor.
//
LED1_ON;
if(CAPT_appHandler()==true)
LED2_ON;
else
LED2_OFF;
LED1_OFF;
//
// This is a great place to add in any
// background application code.
//
__no_operation();
//
// End of background loop iteration
// Go to sleep if there is nothing left to do
//
CAPT_appSleep();
} // End background loop
} // End main()

The main() routine disables the watchdog timer, initializes the MCU via the board support package, and starts the capacitive sensing application. It then spends the rest of its time inside the application background loop. LED1 is toggled whenever the application handler is called, and LED2 is set whenever the CAPT_appHandler() function returns true, indicating that any sensor has a proximity detection. After the app handler runs, a call to CAPT_appSleep() will put the MCU to sleep if there are no pending flags that need to be serviced. The CapTIvate™ conversion timer interrupt will wake the application each time the user interface needs to be refreshed.

Board Support Package (CAPT_BSP)

The board support package configures the MCU for operation with the following parameters:

  • Watchdog timer is disabled
  • 8 MHz DCO Frequency
  • 8 MHz MCLK for zero wait state FRAM execution, sourced from the DCO
  • 2 MHz SMCLK, sourced from the DCO
  • 32 kHz ACLK, sourced from an external crystal (if connected) or the internal REFO
  • USCI_A0 and USCI_B0 are muxed to pins to allow for communication

The port muxing is configured for the CAPTIVATE-FR2633 processor module. The board support package should be ported to the platform and device used in each application!

Example Application (CAPT_App)

The example application demonstrates how to enable a generic capacitive sensing application with or without wake-on-proximity. It includes three functions:

  • CAPT_appStart()
  • CAPT_appHandler()
  • CAPT_appSleep()

CAPT_appStart()

This function provides an example of the functions that need to be called to configure the CapTIvate™ Software Library for operation. It handles the following tasks:

  1. Initializing and calibrating the capacitive sensing UI via the CapTIvate™ Software Library top level API calls
  2. Configuring and starting the CapTIvate™ timer for periodic interrupts

As discussed in the guide for using the top level API, it is necessary to call CAPT_initUI() and CAPT_calibrateUI() when starting a CapTIvate™ software library application. These functions configure the CapTIvate™ peripheral and calibrate all of the elements in the UI. If noise immunity (EMC) functionality is going to be used, it is also necessary to load an EMC configuration structure via a call to CAPT_loadEMCConfig(). Note that in the actual example CAPT_appStart() function, the CAPT_loadEMCConfig() function is a compile-time include.

CAPT_initUI(&g_uiApp);
CAPT_loadEMCConfig(&g_EMCConfig); // (Only needed if EMC features are enabled!)
CAPT_calibrateUI(&g_uiApp);

The integrated CapTIvate™ conversion timer is a periodic timer that can be used to generate an interrupt or directly trigger a conversion at a specified interval. The timer is configured via HAL function calls, as shown below. Note that these HAL functions are available in ROM on devices with CapTIvate™ software in ROM- hence the MAP_ calls. For more information on ROM software, see the ROM section.

MAP_CAPT_stopTimer();
MAP_CAPT_clearTimer();
MAP_CAPT_selectTimerSource(CAPT_TIMER_SRC_ACLK);
MAP_CAPT_selectTimerSourceDivider(CAPT_TIMER_CLKDIV__1);
MAP_CAPT_writeTimerCompRegister(CAPT_MS_TO_CYCLES(g_uiApp.ui16ActiveModeScanPeriod));
MAP_CAPT_startTimer();
MAP_CAPT_enableISR(CAPT_TIMER_INTERRUPT);

These setup functions configure the timer to be sourced from ACLK (32 kHz in this starter project). An input divider of 1 is selected, and the compare register is set to the active mode scan period, converted to cycles. The macro CAPT_MS_TO_CYCLES approximates the number of 32 kHz clock cycles needed to produce the desired scan rate by multiplying the saved value (in milliseconds) by 32 (via a 5x bit shift). The timer interrupt is enabled to start the application. When the timer interrupt is asserted by the timer, the CapTIvate™ peripheral interrupt handler will run. The interrupt handler is in the BASE layer of the CapTIvate™ library, and is available in source code for transparency.

#pragma vector=CAPTIVATE_VECTOR
__interrupt void CAPT_ISR(void)
{
switch(__even_in_range(CAPT_getInterruptVector(), CAPT_IV_MAX_COUNT_ERROR))
{
// End of Conversion Interrupt
case CAPT_IV_END_OF_CONVERSION:
g_bEndOfConversionFlag = true;
break;
// Detection Interrupt
case CAPT_IV_DETECTION:
g_bDetectionFlag = true;
break;
// Timer Interrupt
case CAPT_IV_TIMER:
g_bConvTimerFlag = true;
break;
// Conversion Counter Interrupt
case CAPT_IV_CONVERSION_COUNTER:
g_bConvCounterFlag = true;
break;
// Max Count Error Interrupt
case CAPT_IV_MAX_COUNT_ERROR:
g_bMaxCountErrorFlag = true;
break;
}
__bic_SR_register_on_exit(LPM3_bits);
}

The interrupt handler will set the appropriate flag, and it always exits active. Therefore, the application merely needs to monitor the g_bConvTimerFlag boolean value to known when the timer has tripped, meaning that it is time to update the user interface.

CAPT_appHandler()

The application handler function must be periodically called from the application background loop, as shown in main.c. At first glance, the function appears quite complex- but really, all the application handler does is manage when the UI needs to be updated (in active mode), as well as manage the transitions between active mode and wake-on-proximity mode.

The application handler makes use of several convenience variables in the CapTIvate™ top level application object:

  • The active mode scan period
  • The wake on proximity mode scan period
  • The inactivity time-out
  • The wakeup interval

It may be configured at compile time for two different modes of operation:

  1. Active mode only (No wake-on-proximity state handling). This is the reduced memory footprint option.
  2. Active mode with wake-on-proximity management. This option requires more memory to handle the wake-on-proximity management.

The compile time mode is determined by settings in the user configuration header file (CAPT_UserConfig.h in the CAPT_config directory):

Active Mode Only Configuration

#define CAPT_WAKEONPROX_ENABLE (false)
#define CAPT_WAKEONPROX_SENSOR (none)

Active Mode with Wake-on-Proximity Configuration

#define CAPT_WAKEONPROX_ENABLE (true)
#define CAPT_WAKEONPROX_SENSOR (selected sensor)

These options can and should be configured through the CapTIvate™ Design Center. See the Compile Time Options section for details.

Active mode is characterized by the following behaviour:

  • The CapTIvate™ conversion timer is used in interrupt mode to wake the CPU at a specified interval
  • When the g_bConvTimerFlag is asserted, CAPT_updateUI() is called to refresh all sensors in the UI under CPU control.

Wake-on-proximity mode is characterized by the following behaviour:

  • The CapTIvate™ conversion timer is used in timer-triggered conversion mode to automatically start a conversion of a single time cycle at a specified interval without any CPU intervention
  • When any element in the time cycle selected for wake-on-proximity has a proximity threshold crossing or negative touch threshold crossing, the detection flag (g_bDetectionFlag) is asserted and the application switches to active mode
  • If the conversion counter flag (g_bConvCounterFlag)is asserted, the application switches to active mode
CAPT_appHandler() Compiled for Active Mode Only

The diagram below describes the behaviour of the application handler when compiled for active mode support only.

app_handler_basic.png
CAPT_appHandler() with Active Mode Only Support

When compiled for active mode only, the function is pre-processed down to this basic set of functionality:

bool CAPT_appHandler(void)
{
static bool bActivity = false;
if (g_bConvTimerFlag == true)
{
//
// Clear the conversion timer flag,
// and update the UI
//
g_bConvTimerFlag = false;
CAPT_updateUI(&g_uiApp);
bActivity = CAPT_getGlobalUIProximityStatus(&g_uiApp);
}
return bActivity;
}

Since the CapTIvate™ conversion timer was already configured by CAPT_appStart(), all that is needed is to test the g_bConvTimerFlag. If it is set, then the application handler clears it, updates the UI via CAPT_updateUI(), and checks to see if any elements in the UI have a proximity detection. The function returns true if any element was in proximity, else false.

If communications are enabled, the function also checks for incoming packets as shown below:

bool CAPT_appHandler(void)
{
static bool bActivity = false;
if (g_bConvTimerFlag == true)
{
//
// Clear the conversion timer flag,
// and update the UI
//
g_bConvTimerFlag = false;
CAPT_updateUI(&g_uiApp);
bActivity = CAPT_getGlobalUIProximityStatus(&g_uiApp);
}
//
// If communications are enabled, check for any incoming packets.
//
CAPT_checkForInboundPacket();
//
// Check to see if the packet requested a re-calibration.
// If wake-on-prox is enabled and the current application state
// is wake-on-prox, disable the wake-on-prox feature during the calibration
// and re-enable it after the calibration.
//
if (CAPT_checkForRecalibrationRequest() == true)
{
CAPT_calibrateUI(&g_uiApp);
}
return bActivity;
}

A call is made to CAPT_checkForInboundPacket() to see if any packets have been received that need processing. Some parameter packets (such as a sensor's conversion count) require that a re-calibration take place. The CAPT_checkForRecalibrationRequest() checks to see if a re-calibration request is pending.

Note that if g_bConvTimerFlag is false, the function is essentially non-blocking. This allows the background loop in main() to service other application tasks, while just periodically calling CAPT_appHandler() to see if it is time to do something.

CAPT_appHandler() Compiled for Active Mode with Wake-on-Proximity Mode

When compiled with wake-on-proximity mode enabled, the application handler manages transitions between active mode and wake-on-proximity mode. The application starts in active mode, and follows the flow shown below:

app_handler_wop.png
CAPT_appHandler() with Wake-on-Proximity Support

The wake-on-proximity feature allows for one time cycle to be measured and processed autonomously with no CPU interaction until a detection, counter interrupt, or error condition occurs. This is useful for applications that have a power key or a proximity sensor that is used to wake up the rest of the system, since that sensor may be scanned with no CPU overhead. When a detection does occur, the handler switches operation to active mode and the entire user interface is scanned under CPU control.

The application remains in active mode under CPU control as long as at least one element is in proximity detect. Once all elements are clear of proximity, the session time-out counter begins counting down. This counter will keep the system in active mode for the specified number of samples before returning into wake-on-proximity mode. The timeout is specified in the application object via the ui16InactivityTimeout parameter.

In addition to waking on a detection, it is also possible to periodically wake up into active mode after a certain number of conversions have taken place in wake-on-proximity mode. This is useful to ensure that all the other sensors in the system have current long term averages (LTAs) to account for environmental drift. The conversion counter interrupt may be used to specify a wakeup period.

These wakeup sources are discussed further in the wake-on-proximity description.

bool CAPT_appHandler(void)
{
static uint16_t g_ui16UISessionTimeoutCtr = 1;
static bool bActivity = false;
switch (g_uiApp.state)
{
case eUIActive:
if (g_bConvTimerFlag == true)
{
//
// Clear the conversion timer flag,
// and update the UI
//
g_bConvTimerFlag = false;
CAPT_updateUI(&g_uiApp);
bActivity = CAPT_getGlobalUIProximityStatus(&g_uiApp);
//
// If autonomous mode is enabled, check to
// see if autonomous mode should be entered.
//
if (bActivity == true)
{
//
// If there is still a prox detection,
// reset the session timeout counter.
//
g_ui16UISessionTimeoutCtr = g_uiApp.ui16InactivityTimeout;
}
else if (--g_ui16UISessionTimeoutCtr == 0)
{
//
// If the session has timed out,
// enter autonomous mode
//
g_uiApp.state = eUIWakeOnProx;
bActivity = false;
//
// Set the timer period for wake on touch interval
//
MAP_CAPT_disableISR(CAPT_TIMER_INTERRUPT);
MAP_CAPT_stopTimer();
MAP_CAPT_clearTimer();
MAP_CAPT_writeTimerCompRegister(CAPT_MS_TO_CYCLES(g_uiApp.ui16WakeOnProxModeScanPeriod));
MAP_CAPT_startTimer();
g_bConvTimerFlag = false;
CAPT_startWakeOnProxMode(
&CAPT_WAKEONPROX_SENSOR,
0,
g_uiApp.ui8WakeupInterval
);
}
}
break;
case eUIWakeOnProx:
if (g_bDetectionFlag || g_bConvCounterFlag || g_bMaxCountErrorFlag)
{
//
// If a detection, conversion counter, or max count error flag was set,
// stop autonomous mode and reload an active session
//
CAPT_stopWakeOnProxMode(&CAPT_WAKEONPROX_SENSOR, 0);
g_bDetectionFlag = false;
g_bConvCounterFlag = false;
g_bMaxCountErrorFlag = false;
g_uiApp.state = eUIActive;
g_ui16UISessionTimeoutCtr = g_uiApp.ui16InactivityTimeout;
//
// Set the timer period for normal scan interval
//
MAP_CAPT_disableISR(CAPT_TIMER_INTERRUPT);
MAP_CAPT_stopTimer();
MAP_CAPT_clearTimer();
MAP_CAPT_writeTimerCompRegister(CAPT_MS_TO_CYCLES(g_uiApp.ui16ActiveModeScanPeriod));
MAP_CAPT_startTimer();
CAPT_clearIFG(CAPT_TIMER_INTERRUPT);
MAP_CAPT_enableISR(CAPT_TIMER_INTERRUPT);
}
break;
}
return bActivity;
}

Note that it is possible to set a different scan period for wake-on-proximity mode than the period used in active mode. This allows for slow scanning while waiting for proximity, and faster scanning when a user is detected. Scanning less often reduces the overall power consumption. For more details on designing for low power, see the low power design guide.

CAPT_appSleep()

The application sleep function is a safety wrapper that ensures no flags are pending when the application transitions into a low power mode. Safety is achieved by disabling interrupts, testing the flags, and then entering a low power mode while simultaneously re-enabling interrupts. This sequence protects the application from any software race conditions that might occur, such as a flag being set after the flag was tested but before the device entered low power mode.

__bic_SR_register(GIE);
if (!(g_bConvTimerFlag ||g_bDetectionFlag || g_bConvCounterFlag || g_bMaxCountErrorFlag))
{
__bis_SR_register(g_uiApp.ui8AppLPM | GIE);
}
else
{
__bis_SR_register(GIE);
}

Moving Forward

That's it! The example application in captivate_app is meant to be just that- an example. Feel free to modify it to suite the needs of another application. For details on how to use the CapTIvate™ Software Library top level API, see Using the Top Level API.

Adding CapTIvateā„¢ to an Existing Project

If you have an existing application using an MSP MCU and would like to add CapTIvate™ capacitive touch sensing capability to that project, this guide will discuss the important steps to take to make the integration as seamless as possible.

This guide also provides details about how to set up a new project from scratch, if the CapTIvate™ starter project is not being used.

Similar Devices

The CapTIvate™ MSP430FR26xx and MSP430FR25xx MCUs have a similar platform architecture as some other MSP430 FRAM devices. As such, porting an existing application from another FRAM device to an MSP430FR26xx or MSP430FR25xx MCU does not require significant effort. MCUs that are very compatible include the MSP430FR4133, MSP430FR2433, and MSP430FR2033.

Porting Approach

The best way to begin CapTIvate™ software library development, regardless of whether the capacitive sensing functionality will be integrated with another project or not, is to generate a starter project with the CapTIvate™ Design Center. This starter project may then be used by itself to quickly bring up a capacitive sensing application and experiment with tuning. Once that process is complete, all that is needed is to bring over the necessary CapTIvate™ software components from the starter project into the existing software project.

The previous section discusses how to generate a starter project. The new sensor project design workshop also provides a step-by-step overview of the process. Once you have a starter project, you can extract the CapTIvate™ software library components from the starter project and integrate them into the existing project.

Bringing over CapTIvate™ Software Components from a Starter Project to an Existing Project

As an example, this section will discuss bringing over the CapTIvate™ software components from a starter project to an existing software project. This example will be discussed in the context of TI's Code Composer Studio (CCS) IDE. The same principles apply to an IAR Embedded Workbench project. In this example, the existing software project will be an empty CCS project, as shown below:

existing_project_files_PartA.png
Empty Application

main.c

#include <msp430.h>
void main(void) {
WDTCTL = WDTPW | WDTHOLD;
PM5CTL0 &= ~LOCKLPM5;
while(1)
{
__no_operation();
}
}

To bring over CapTIvate™, the following steps are required:

  1. Copy over the captivate, captivate_config, and driverlib directories and all of their content from the starter project to the existing application
  2. Configure the existing project's compiler settings for CapTIvate™
  3. Configure the existing project's linker settings for CapTIvate™
  4. Add top level API calls to the main application to initialize, calibrate, and begin updating the newly added capacitive sensing interface
  5. [Optional] Optimize the clock system configuration for CapTIvate™

Step 1: Copying the Needed CapTIvate™ Files

  • 1a. Copy the captivate, captivate_config, and driverlib directories and all of their content from the starter project to the existing application
  • 1b. Copy the driverlib directory and all of its contents from the starter project to the existing application

Below are the directories that need to be copied from the starter project:

starter_project_files_transfer.png
Starter Application Files To Copy

Step 2: Configuring the Existing Project's Compiler Settings for CapTIvate™

  • 2a. Add the captivate, captivate/BASE, captivate/ADVANCED, captivate/COMM, and captivate_config project directories to the existing project's include search path, as shown below. Also add the DriverLib directory driverlib/MSP430FR2x_4xx
lib_existing_project_includes.png
Compiler Include Settings
  • 2b. Change the existing project's compiler processor options settings to use the small code, small data memory model. This is required for the project to be compatible with the ROM and object code library.
lib_existing_project_scsd.png
Compiler Processor Options
  • 2c. Add the following definitions to the project's compiler predefined symbol list:
    • TARGET_IS_MSP430FR2633 (To include the CapTIvate™ ROM functions)
    • TARGET_IS_MSP430FR2XX_4XX (To include the DriverLib ROM functions)
lib_existing_project_defines.png
Compiler Pre-Defined Symbols

Step 3: Configuring the Existing Project's Linker Settings for CapTIvate™

  • 3a. Add the CapTIvate™ library archive to the linker's input. If you are using the pre-compiled DriverLib directory from the starter project, add the DriverLib library archive as well, as shown below.
lib_existing_project_linker.png
Linker Inputs

Step 4: Add Top Level API Calls to Begin Using CapTIvate™

  • 4a. Insert calls to initialize and calibrate the user interface once at the beginning of the application. Be sure to also add the #include statement for the CapTIvate library.
// Step 4a:
CAPT_initUI(&g_uiApp);
CAPT_calibrateUI(&g_uiApp);

If noise immunity (EMC) features are going to be enabled for this design, it is also necessary to link the EMC configuration structure to the EMC module via a call to CAPT_loadEMCConfig(). This must be done before calling CAPT_calibrateUI(), so that the EMC configuration parameters are available during the calibration process.

// Step 4a with EMC:
CAPT_initUI(&g_uiApp);
CAPT_loadEMCConfig(&g_EMCConfig);
CAPT_calibrateUI(&g_uiApp);
  • 4b. Configure the CapTIvate™ interval timer to periodically set the g_bConvTimerFlag status flag.
// Step 4b:
MAP_CAPT_selectTimerSource(CAPT_TIMER_SRC_ACLK);
MAP_CAPT_writeTimerCompRegister(CAPT_MS_TO_CYCLES(g_uiApp.ui16ActiveModeScanPeriod));
MAP_CAPT_startTimer();
MAP_CAPT_enableISR(CAPT_TIMER_INTERRUPT);
  • 4c. Begin updating the UI! Add a test in the background loop to see if the conversion timer flag has been set. If it has, clear it and update the user interface via CAPT_updateUI().

main.c

#include <msp430.h>
#include "captivate.h"
void main(void) {
WDTCTL = WDTPW | WDTHOLD;
PM5CTL0 &= ~LOCKLPM5;
// Step 4a:
CAPT_initUI(&g_uiApp);
CAPT_calibrateUI(&g_uiApp);
// Step 4b:
MAP_CAPT_selectTimerSource(CAPT_TIMER_SRC_ACLK);
MAP_CAPT_writeTimerCompRegister(CAPT_MS_TO_CYCLES(g_uiApp.ui16ActiveModeScanPeriod));
MAP_CAPT_startTimer();
MAP_CAPT_enableISR(CAPT_TIMER_INTERRUPT);
while(1)
{
// Step 4c:
if (g_bConvTimerFlag == true)
{
g_bConvTimerFlag = false;
CAPT_updateUI(&g_uiApp);
}
}
}
  • 4d. Check the touch status of the sensor by testing its bSensorTouch flag. This flag will indicate if any element in the sensor has a touch detection. After CAPT_updateUI() is called, all of the sensor and element objects in the UI will have updated status variables.
while(1)
{
// Step 4c:
if (g_bConvTimerFlag == true)
{
g_bConvTimerFlag = false;
CAPT_updateUI(&g_uiApp);
// Step 4d:
if (keypad.bSensorTouch == true)
{
__no_operation();
}
}
}

Step 5: [Optional] Optimizing the Clock System for CapTIvate™

The default clock frequency for the DCO is approximately 1 MHz. The CapTIvate™ Software Library runs most efficiently at 8 MHz. At 8 MHz, no memory access wait states are required, providing an efficient uA/MHz ratio. To increase the DCO clock frequency to 8 MHz, insert calls to DriverLib to configure the clock system as shown below. It is best to configure the clock system before any calls to the CapTIvate™ Software Library.

#define MCLK_FREQ 8000000
#define FLLREF_FREQ 32768
#define FLL_RATIO (MCLK_FREQ / FLLREF_FREQ)
// Step 5:
CS_initClockSignal(CS_FLLREF, CS_REFOCLK_SELECT, CS_CLOCK_DIVIDER_1);
CS_initClockSignal(CS_ACLK, CS_REFOCLK_SELECT, CS_CLOCK_DIVIDER_1);
CS_initClockSignal(CS_MCLK, CS_DCOCLKDIV_SELECT, CS_CLOCK_DIVIDER_1);
CS_initClockSignal(CS_SMCLK, CS_DCOCLKDIV_SELECT, CS_CLOCK_DIVIDER_4);
CS_initFLLSettle((MCLK_FREQ/1000), FLL_RATIO);
while (CS_getFaultFlagStatus(CS_DCOFFG | CS_FLLULIFG))
{
CS_clearFaultFlag(CS_DCOFFG | CS_FLLULIFG);
}

Note that SMCLK is configured to run at DCO/4, or 2 MHz. This is the frequency that the CapTIvate™ Software Library COMM module expects in order to generate UART baud rates correctly.

Step 6: [Optional] Muxing IO for Communications

To add the ability to communicate with the CapTIvate™ Design Center, it is necessary to configure the CapTIvate™ user configuration for a communication interface (UART or I2C) and mux the appropriate peripheral to device pins. See the appropriate device datasheet and device family user's guide for information on how to mux digital functions to device pins. The starter project also provides an example of how to mux the eUSCI_A0 and eUSCI_B0 peripherals on the MSP430FR2633.

Completed Example Application

Below is the completed example:

main.c

#include <msp430.h>
#include "captivate.h"
#include "driverlib.h"
#define MCLK_FREQ 8000000
#define FLLREF_FREQ 32768
#define FLL_RATIO (MCLK_FREQ / FLLREF_FREQ)
void main(void) {
WDTCTL = WDTPW | WDTHOLD;
PM5CTL0 &= ~LOCKLPM5;
// Step 5:
CS_initClockSignal(CS_FLLREF, CS_REFOCLK_SELECT, CS_CLOCK_DIVIDER_1);
CS_initClockSignal(CS_ACLK, CS_REFOCLK_SELECT, CS_CLOCK_DIVIDER_1);
CS_initClockSignal(CS_MCLK, CS_DCOCLKDIV_SELECT, CS_CLOCK_DIVIDER_1);
CS_initClockSignal(CS_SMCLK, CS_DCOCLKDIV_SELECT, CS_CLOCK_DIVIDER_4);
CS_initFLLSettle((MCLK_FREQ/1000), FLL_RATIO);
while (CS_getFaultFlagStatus(CS_DCOFFG | CS_FLLULIFG))
{
CS_clearFaultFlag(CS_DCOFFG | CS_FLLULIFG);
}
// Step 4a:
CAPT_initUI(&g_uiApp);
CAPT_calibrateUI(&g_uiApp);
// Step 4b:
MAP_CAPT_selectTimerSource(CAPT_TIMER_SRC_ACLK);
MAP_CAPT_writeTimerCompRegister(CAPT_MS_TO_CYCLES(g_uiApp.ui16ActiveModeScanPeriod));
MAP_CAPT_startTimer();
MAP_CAPT_enableISR(CAPT_TIMER_INTERRUPT);
while(1)
{
// Step 4c:
if (g_bConvTimerFlag == true)
{
g_bConvTimerFlag = false;
CAPT_updateUI(&g_uiApp);
// Step 4d:
if (keypad.bSensorTouch == true)
{
__no_operation();
}
}
}
}

How-To

The how-to section of the CapTIvate™ Software Library chapter contains basic code snippets that demonstrate how to perform a simple task, such as measuring a sensor, checking the status of a sensor, or accessing raw data.

Use the Top Level API

The top level API of the CapTIvate™ Software Library provides a very simple, highly abstracted programming interface to the library. To get an application up and running, it is only necessary to have knowledge of three basic functions: CAPT_initUI(), CAPT_calibrateUI(), and CAPT_updateUI().

Scope

As introduced in the programming model section, the top level API functions operate solely on the top level application object, or tCaptivateApplication. This object contains all of the information that is needed to run the user interface. It contains the links to all of the sensors that are in the UI. It is important to understand that when a top level API function is used, all of the sensors that are associated with the application are affected. For example, calling CAPT_calibrateUI() causes each sensor in the UI to be calibrated.

Open Source

The top level API functions are delivered as open source functions in the library, so that the software designer can understand how the functions work. The functions exist in the CAPT_Manager.c and CAPT_Manager.h files, which are a part of the ADVANCED module of the library.

Setting up an Application

When setting up an application, it is important to note that there are two types of functions in the top level API:

  • Initialization functions (Run one time at start-up)
  • Periodic functions (called periodically while the application is running to do something)

The initialization functions are CAPT_initUI() and CAPT_calibrateUI(). CAPT_updateUI() is a periodic function. The initialization functions must be called at the beginning of the application. The behavior of each function is described below.

CAPT_initUI()

The CAPT_initUI() function is responsible for the following actions at start-up:

  1. Powering on of the CapTIvate™ peripheral
  2. Initialization of the CapTIvate™ peripheral global settings
  3. Configuration of each sensor's IO
  4. Initialization of each sensor
  5. If communications are enabled, initialization of the library's communication (COMM) module

Essentially, this function takes care of the one-time settings that do not change once the application is up and running. It should be called one time before any other CapTIvate™ library function is called.

CAPT_calibrateUI()

The CAPT_calibrateUI() function is responsible for obtaining coarse gain, fine gain, and offset subtraction calibration values for every element in every sensor in the application. Once these calibration values are obtained via this function call, they will be applied every time an element is measured to provide the correct amount of gain and offset. For details on how gain and offset parameters work, see the peripheral section of the technology guide.

This top-level calibration function is also responsible for determining whether to use the standard calibration routine (CAPT_calibrateSensor()) or the EMC calibration routine (CAPT_calibrateSensorWithEMC()), depending on whether or not noise immunity is enabled. If noise immunity is enabled, the EMC calibration routine provides calibration values at multiple conversion frequencies and performs additional self-test functions.

This function must be called before any UI or sensor update functions are called- otherwise, no calibration values will be present!

In addition to the first call, in certain applications it may be desirable to force a re-calibration at run-time. One example of this scenario is a mobile device that experiences a "negative" or "reverse" touch. If a user is touching a sensor during power-up or reset, the sensor will be calibrated to the touched state rather than the un-touched state. When the user lets go of the sensor, the measurement will change rapidly against the expected direction of change. This can be interpreted as a reverse touch scenario, after which it may be desirable to re-calibrate the entire user interface to ensure a good starting point.

CAPT_updateUI()

The CAPT_updateUI() function is responsible for the following actions:

  1. Updating each sensor
  2. Testing each sensor for a re-calibration condition
  3. If communications are enabled, attempting transmission of element and/or sensor data via the COMM module

This top-level update function is also responsible for determining whether to use the standard update routine (CAPT_updateSensor())or the EMC update routine (CAPT_updateSensorWithEMC()), depending on whether or not noise immunity is enabled.

This function should be called periodically to update all of the sensors in the system. After this function is called, the following values are updated:

  • Raw count
  • Filtered count
  • Long term average (LTA)
  • Detect and negative touch flags
  • Touch and proximity flags
  • Previous touch flags
  • Dominant element ID (for a button group)
  • Position (for a slider or wheel)
  • Max count error flags and noise state flags
  • De-bounce counters

After the values are updated via a call to CAPT_updateUI(), any value can be checked by referencing it in the appropriate data structure.

Simple Code Example

The example below demonstrates the structure of a typical application. For simplicity, this example application continuously measures the UI and does not go to sleep in between scans. To enable scheduled scanning, it is necessary to use a timer to trigger the update, such as the CapTIvate™ interval timer.

// Execute one-time setup functions
CAPT_initUI(&g_uiApp);
CAPT_calibrateUI(&g_uiApp);
while(1)
{
// Continuously update the UI
CAPT_updateUI(&g_uiApp);
}

Register a Callback Function

Callbacks provide a mechanism for the application to be notified when a sensor has been updated. The application must first register its "callback" function for each sensor before it can receive updates. When the callback is executed, the application can query the sensor's data structure to determine the status of the sensor.

The library function CAPT_registerCallback() provides the registration.

Format

Sensor callback functions are passed a pointer to the calling sensor, and they must return void. An example skeleton callback function is shown below:

void my_button_callback(tSensor* pSensor)
{
// DO SOMETHING ...
}

Example

This is what it would look like to register the callback function named "my_button_callback" to the sensor BTN0000.

MAP_CAPT_registerCallback(&BTN0000, &my_button_callback);

Once an application's callback function is registered, the callback is executed each time the corresponding sensor is scanned and processed, regardless if a proximity or touch detection has occurred. Inside the user callback, the application can perform any required sensor data and status post-processing. In a typical button application, this is where the application will check for a proximity, touch detection or slider/wheel position.

Example

Here is a typical callback example for a button that checks both when a touch is detected and a release.

void my_button_callback(tSensor* pSensor)
{
if((pSensor->bSensorTouch == true) && (pSensor->bSensorPrevTouch == false))
{
// BUTTON PRESSED
// DO SOMETHING ...
}
else if((pSensor->bSensorTouch == false) && (pSensor->bSensorPrevTouch == true))
{
// BUTTON RELEASED
// DO SOMETHING ...
}
}

Here is a callback example for a proximity sensor.

void my_proximity_callback(tSensor* pSensor)
{
if(pSensor->bSensorProx == true)
{
// PROXIMITY DETECTED
// DO SOMETHING ...
}
}

In addition to proximity and touch status, sliders and wheels also provide position status. Here is a typical callback example for a slider or wheel that checks a sensor's position.

void my_slider_callback(tSensor* pSensor)
{
uint16_t ui16Position;
// FIRST CHECK IF THERE IS VALID TOUCH
if(pSensor->bSensorTouch == true)
{
// THEN GET THE CURRENT TOUCH POSITION ON THE SLIDER/WHEEL
ui16Position = (uint16_t)((tSliderSensorParams*)pSensor->pSensorParams)->SliderPosition.ui16Natural;
// DO SOMETHING WITH POSITION ...
}
}

Access Element State Data

Each element in a sensor has a set of boolean state flags that indicate its status. The following flags are provided:

  • Detection
  • Negative touch detection
  • Proximity
  • Touch
  • Built-in-self-test (BIST) status
  • Noise status

When a sensor is updated (via CAPT_updateUI(), CAPT_updateSensor(), or CAPT_updateSensorWithEMC()), these status flags are updated for every element within the sensor. There are multiple ways to retrieve the data for processing.

Accessing Element State Data Directly

It is possible to access element state data directly in an element's data structure. To do this, it is necessary to know the name of the variable in the element. An example is shown below that uses element 0 of a sensor named keypad to control some other function, such as illuminating an LED.

extern tElement keypad_E00;
void updateLED(void)
{
if(keypad_E00.bTouch == true)
{
// ILLUMINATE LED
}
else
{
// TURN LED OFF
}
}

Accessing Element State Data Indirectly

Note that in the example above, it was necessary to forward declare keypad_E00. It is also possible to "look up" E00 of the keypad sensor though the parent sensor structure, as shown below. All sensor structures are forward declared in the user configuration header file (CAPT_UserConfig.h), and do not need to be re-declared. The element of interest is accessed via the cycle pointer array and the element pointer array of that cycle.

void updateLED(void)
{
if(keypad.pCycle[0]->pElements[0]->bTouch == true)
{
// ILLUMINATE LED
}
else
{
// TURN LED OFF
}
}

Generating a Status Bit Field for all Elements in a Sensor

The above access methods shown are simple and do not require very much memory. However, they only provide data for one element. The library function CAPT_getElementStateBitField() returns a bit field in which each element is represented with a bit position. The bit field supports up to 64 elements. The return type is a 64-bit unsigned integer, which may be casted down to the size that is needed for the application.

Elements are mapped to bit positions starting with the first element of the first cycle to the last element of the last cycle. For example, a sensor with two cycles and two elements in each cycle would have the following mapping:

  • Return value bit 0 (0x01): Cycle 0 Element 0
  • Return value bit 1 (0x02): Cycle 0 Element 1
  • Return value bit 2 (0x04): Cycle 1 Element 0
  • Return value bit 3 (0x08): Cycle 1 Element 1

This function may be used to query any of the element status flags. The example below tests the touch status flag. If element 0 and element 1 (BIT0 and BIT1), or 0x03, are touched, the LED would be illuminated.

void updateLED(void)
{
uint8_t multiTouchState;
multiTouchState = (uint8_t)CAPT_getElementStateBitField(&keypad, eTouchStatus);
// If '0x03', or element 0 and element 1, are both in detect:
if (multiTouchState & 0x03)
{
// ILLUMINATE LED
}
else
{
// LED OFF
}
}

Accessing a Sensor's Global Flags

Many of the flags that are available at the element level are also available as global flags at the sensor level. The global, sensor-level flags operate as a logical OR of all elements in the sensor. In other words, if any element's flag is set, the sensor's global flag is also set. It this way, it is possible to quickly test one flag to see if anything is happening with a sensor. Then, if something is, the element flags can be used to identify which element(s) threw the flag. In the example below, the LED would be illuminated if any element in the sensor was touched.

void sensorHandler(tSensor* pSensor)
{
if(pSensor->bSensorTouch == true)
{
// ILLUMINATE LED
}
else
{
// TURN LED OFF
}
}

In addition to the global sensor touch flag (bSensorTouch), there is also a global sensor previously touched flag (bSensorPrevTouch). This flag is set if bSensorTouch was set on the previous sample. This can be used as a mechanism to determine if a touch is new (meaning someone just touched the button) versus stale (meaning the touch on this sample is a continuation of a previously started touch). This allows for toggling between states, as shown below:

void sensorHandler(tSensor* pSensor)
{
if((pSensor->bSensorTouch == true) && (pSensor->bSensorPrevTouch == false))
{
// TOGGLE A FUNCTION
}
}

Element Status Flag Reference Table

The table below lists the available status flags that all elements have, and the name of the parameter to use when accessing it.

DescriptionElement Structure ParameterCAPT_getElementStateBitField() Parameter
Touch Detection.bToucheTouchStatus
Proximity Detection.bProxeProxStatus
Negative Touch Detect.bNegativeToucheNegativeTouchStatus
Detect.bDetecteDetectStatus
Built-in-self-test Fail.bBISTFaileBISTStatus
Noise Detected.bNoiseDetectedeNoiseStatus

The detect status is the un-debounced state of the prox status. When an element is in detect but not in prox, this means that the long term average tracking filter is disabled, but the state change into proximity detection is not yet complete because it is currently being de-bounced.

Access the Dominant Button

Button group sensors output a dominant button ID that corresponds to the element with the highest delta response. This is useful for keypads which do not require multi-touch but would like to have some level of nearby key rejection. For example, if a user is touching in between to keys, the dominant key with the highest delta response will be reported.

When a button group sensor is updated (via CAPT_updateUI(), CAPT_updateSensor(), or CAPT_updateSensorWithEMC()), the dominant element value is updated.

Elements are mapped to IDs starting with the first element of the first cycle to the last element of the last cycle. For example, a sensor with two cycles and two elements in each cycle would have the following mapping:

  • 0: Cycle 0 Element 0
  • 1: Cycle 0 Element 1
  • 2: Cycle 1 Element 0
  • 3: Cycle 1 Element 1

Accessing the Dominant Button Directly

The example below shows how to directly access the value in the tButtonSensorParams structure. The LED is illuminated if a touch is present and the dominant key is the first element.

extern tButtonSensorParams keypadSensor_Params;
void updateLED(void)
{
if (keypadSensor.bSensorTouch == true)
{
if (keypadSensor_Params.ui16DominantElement == 0x00)
{
// ILLUMINATE LED
}
else
{
// TURN OFF LED
}
}
else
{
// TURN OFF LED
}
}

Accessing the Dominant Button Indirectly

Note that in the example above, it was necessary to forward declare keypadSensor_Params. It is also possible to "look up" these parameter structures through the parent sensor structure, as shown below. All sensor structures are forward declared in the user configuration header file (CAPT_UserConfig.h), and do not need to be re-declared. It is necessary to type-cast the parameter structure based on the type of sensor.

void updateLED(void)
{
uint8_t dominantButton;
if (keypadSensor.bSensorTouch == true)
{
dominantButton = ((tButtonSensorParams*)(keypadSensor.pSensorParams))->ui16DominantElement;
if (dominantButton == 0x00)
{
// ILLUMINATE LED
}
else
{
// TURN OFF LED
}
}
else
{
// TURN OFF LED
}
}

Accessing the Dominant Button with a Function Call

The final way to access the dominant button value is via a function call to CAPT_getDominantButton() or CAPT_getDominantButtonAddr(). The former function returns the ID of the dominant button, while the latter function returns the memory address (essentially a pointer to) the dominant element.

The example below demonstrates accessing the dominant button ID via a function call.

void updateLEDs(void)
{
uint8_t dominantElement;
if ((keypadSensor.bSensorTouch==true) && (keypadSensor.bSensorPrevTouch==false))
{
dominantElement = CAPT_getDominantButton(&keypadSensor);
if (dominantElement == 0)
{
LED1_OFF;
LED2_OFF;
}
else if (dominantElement == 1)
{
LED1_ON;
LED2_OFF;
}
else if (dominantElement == 2)
{
LED1_OFF;
LED2_ON;
}
else if (dominantElement == 3)
{
LED1_ON;
LED2_ON;
}
}
}

The example below demonstrates processing of the dominant button based on a pointer to the dominant element.

extern tElement keypadSensor_E00;
extern tElement keypadSensor_E01;
extern tElement keypadSensor_E02;
extern tElement keypadSensor_E03;
void updateLEDs(tSensor *sensor)
{
tElement* dominantElement;
if ((keypadSensor.bSensorTouch==true) && (keypadSensor.bSensorPrevTouch==false))
{
dominantElement = CAPT_getDominantButtonAddr(&keypadSensor);
if (dominantElement == &keypadSensor_E00)
{
LED1_OFF;
LED2_OFF;
}
else if (dominantElement == &keypadSensor_E01)
{
LED1_ON;
LED2_OFF;
}
else if (dominantElement == &keypadSensor_E02)
{
LED1_OFF;
LED2_ON;
}
else if (dominantElement == &keypadSensor_E03)
{
LED1_ON;
LED2_ON;
}
}
}

Access Slider or Wheel Position Data

Slider and wheel sensors output a position in addition to touch and proximity status. The position value is available as a IQ16-style value, with 16 integer bits and 16 fractional bits. For almost all applications, the integer bits are the bits of interest, and the fractional bits are merely there to support filtering.

When a slider or wheel sensor is updated (via CAPT_updateUI(), CAPT_updateSensor(), or CAPT_updateSensorWithEMC()), the position value is updated.

NOTE: When a slider or wheel sensor is not being touched, a value of 0xFFFF (UINT16_MAX) is reported.

Accessing the Slider or Wheel Position Directly

The example below shows how to directly access the slider and wheel position parameters in the tSliderSensorParams and tWheelSensorParams structures, respectively. The application assigns the values to the speakerVolume and optionSelection variables, which are intended to represent functionality in an application.

uint16_t speakerVolume;
uint16_t optionSelection;
extern tSliderSensorParams volumeSlider_Params;
extern tWheelSensorParams scrollWheel_Params;
void updateVolumeAndOptionSelection(void)
{
// Set the speaker volume to the natural (integer) 16 bit slider position
if (volumeSlider_Params.SliderPosition.ui16Natural != UINT16_MAX)
{
speakerVolume = volumeSlider_Params.SliderPosition.ui16Natural;
}
// Set the option selection to the natural (integer) 16 bit wheel position
if (scrollWheel_Params.SliderPosition.ui16Natural != UINT16_MAX)
{
optionSelection = scrollWheel_Params.SliderPosition.ui16Natural;
}
}

Accessing the Slider or Wheel Position Indirectly

Note that in the example above, it was necessary to forward declare volumeSlider_Params and scrollWheel_Params. It is also possible to "look up" these parameter structures through the parent sensor structure, as shown below. All sensor structures are forward declared in the user configuration header file (CAPT_UserConfig.h), and do not need to be re-declared. It is necessary to type-cast the parameter structure based on the type of sensor.

uint16_t speakerVolume;
uint16_t optionSelection;
void updateVolumeAndOptionSelection(void)
{
uint16_t position;
// Set the speaker volume to the natural (integer) 16 bit slider position
position = ((tSliderSensorParams*)(volumeSlider.pSensorParams))->SliderPosition.ui16Natural;
if (position != UINT16_MAX)
{
speakerVolume = position;
}
// Set the option selection to the natural (integer) 16 bit wheel position
position = ((tWheelSensorParams*)(scrollWheel.pSensorParams))->SliderPosition.ui16Natural;
if (position != UINT16_MAX)
{
optionSelection = position;
}
}

Note that for both slider and wheel parameter structures, the parameter for position is called SliderPosition. This is because both of these sensor types utilize the same processing algorithm.

Accessing the Slider or Wheel Position with a Function Call

The final (and simplest) way to access slider or wheel position is via a function call to CAPT_getSensorPosition(). This function will return 0xFFFF (UINT16_MAX) if no touch is present.

uint16_t speakerVolume;
uint16_t optionSelection;
void updateVolumeAndOptionSelection(void)
{
uint16_t position;
// Set the speaker volume to the natural (integer) 16 bit slider position
position = CAPT_getSensorPosition(&volumeSlider);
if (position != UINT16_MAX)
{
speakerVolume = position;
}
// Set the option selection to the natural (integer) 16 bit wheel position
position = CAPT_getSensorPosition(&scrollWheel);
if (position != UINT16_MAX)
{
optionSelection = position;
}
}

Using the function call allows the software implementation to be clearer, at the penalty of function overhead.

Access Element Measurement Data

Each element in a sensor has several variables that contain the current measurement data. The following values are provided:

  • Filtered Count (The conversion result)
  • Long Term Average (LTA) (The baseline reference)
  • Raw Count(s) (The raw data sample before any processing is applied)
  • Composite Raw Count (The composite data sample of a multi-frequency or oversampled conversion)
  • Previous Composite Raw Count (The previous composite data sample of a multi-frequency or oversampled conversion)

When a sensor is updated (via CAPT_updateUI(), CAPT_updateSensor(), or CAPT_updateSensorWithEMC()), these variables are updated for every element within the sensor. There are multiple ways to retrieve the data.

Accessing Element State Data Directly

It is possible to access element measurement data directly in an element's data structure. To do this, it is necessary to know the name of the variable in the element. An example is shown below that uses element 0 of a sensor named keypad.

extern tElement keypad_E00;
void myDataFunction(void)
{
uint16_t data;
// Place the filtered count value into the 'data' variable.
// Note that the filtered count value is an IQ16 format variable with 16 integer bits and 16 fractional bits.
// Typically, only the integer bits are of interest.
data = keypad_E00.filterCount.ui16Natural;
__no_operation();
// Place the long term average into the 'data' variable.
// Note that the LTA is an IQ16 format variable with 16 integer bits and 16 fractional bits.
// Typically, only the integer bits are of interest.
data = keypad_E00.LTA.ui16Natural;
__no_operation();
// Place the raw count value into the 'data' variable.
// Note that there may be either 1 or 4 raw count variables, depending on whether the conversion
// type used is single or multi-frequency.
data = keypad_E00.pRawCount[0];
__no_operation();
// If the conversion is multi-frequency, then keypad_E00.pRawCount[0] holds frequency 0's result,
// and keypad_E00.pRawCount[3] holds frequency 3's result.
// The raw composite result of all 4 frequencies is placed in the keypad_E00.ui16CompositeRawCount variable.
data = keypad_E00.ui16CompositeRawCount;
__no_operation();
}

Accessing Element Measurement Data Indirectly

Note that in the example above, it was necessary to forward declare keypad_E00. It is also possible to "look up" E00 of the keypad sensor though the parent sensor structure, as shown below. All sensor structures are forward declared in the user configuration header file (CAPT_UserConfig.h), and do not need to be re-declared. The element of interest is accessed via the cycle pointer array and the element pointer array of that cycle.

void myDataFunction(void)
{
uint16_t data;
// Place the filtered count value into the 'data' variable.
// Note that the filtered count value is an IQ16 format variable with 16 integer bits and 16 fractional bits.
// Typically, only the integer bits are of interest.
data = keypad.pCycle[0]->pElements[0]->filterCount.ui16Natural;
__no_operation();
}

Update Sensors Independently

Up until this point, all discussion around measuring sensors has been via the top level API- specifically, the CAPT_updateUI() call. When using the CAPT_updateUI() function call, all sensors in the application are updated. For most applications, this is the desired operation. However, there are cases where it may be desired to update sensors individually or at different rates. This how-to explains the function calls that are used to individually update sensors. There are two function calls available for updating a sensor: CAPT_updateSensor() and CAPT_updateSensorWithEMC().

CAPT_updateSensor()

This is the standard sensor update function. After calling this function, the following values are updated for the passed sensor only:

  • Raw counts
  • Filtered counts
  • Long term averages (LTAs)
  • Detect and negative touch flags
  • Touch and proximity flags
  • Previous touch flags
  • Dominant element ID (for a button group)
  • Position (for a slider or wheel)
  • Max count error flags and noise state flags
  • De-bounce counters

Below is the syntax used to call the function. The parameters include a pointer to the sensor to update, and the low power mode bits to set during the conversion process. LPM0, LPM1, LPM2, and LPM3 may be used.

CAPT_updateSensor(&keypadSensor, LPM0_bits);

CAPT_updateSensorWithEMC()

The EMC version of the sensor update call provides the same end functionality as the standard call, with the exception that EMC plug-ins from the advanced layer are applied. When this function is used, the EMC configuration structure defines the style of conversion to use. This may mean that multi-frequency scanning and/or oversampling is utilized.

The syntax for the EMC version is identical to the standard version, as shown below. This is to provide a standard call so that application code does not need to change significantly to accommodate switching to an EMC scanning mode. All EMC plug-in configuration is controlled by the EMC configuration structure.

CAPT_updateSensorWithEMC(&keypadSensor, LPM0_bits);

Important Functionality to Handle

When the sensor update functions are used rather than the top level API, several other tasks need to be handled by the application, such as testing for a re-calibration condition or transmitting data.

Testing for Re-Calibration

Over time, the long term average of a sensor may drift. To ensure that consistent sensitivity is always provided, the software library provides a mechanism to test to see if any element in a sensor has drifted outside of an acceptable boundary. This mechanism is the CAPT_testForRecalibration() function. The CAPT_updateUI() function takes care of handling this when the top level API is used, but if sensors are updated individually then this needs to be handled by the application.

The typical handling method is shown below:

// Update the individual sensor(s)
CAPT_updateSensor(&keypadSensor, LPM0_bits);
// Test for a re-calibration condition
if (CAPT_testForRecalibration(&keypadSensor) == true)
{
// If a re-calibration is required, perform it now
CAPT_calibrateSensor(&keypadSensor);
}

For details on how the re-calibration test works, see the runtime re-calibration definition.

Transmitting Sensor and Element Data

If communication via the COMM module is desired, it is nescessary to add the calls to transmit the sensor and element data for this sensor via the COMM module.

//
// If the UART or Bulk I2C interface is enabled, write out element
// and sensor data.
//
#if ((CAPT_INTERFACE==__CAPT_UART_INTERFACE__)||\
(CAPT_INTERFACE==__CAPT_BULKI2C_INTERFACE__))
CAPT_writeElementData(x);
CAPT_writeSensorData(x);
#endif

In this code example, 'x' represents the sensor's integer ID. This ID is the position of the sensor in the global sensor pointer array. This is the array that the COMM module uses to look up sensors.

For the CAPTIVATE-BSWP demo panel, the array looks like this:

tSensor* g_pCaptivateSensorArray[CAPT_SENSOR_COUNT] =
{
&keypadSensor,
&proximitySensor,
&sliderSensor,
&wheelSensor,
};

Thus, the ID of the keypad sensor would be 0.

Full Implementation

Putting it all together, updating a sensor individually would have the following progression:

// Update the individual sensor(s)
CAPT_updateSensor(&keypadSensor, LPM0_bits);
// Test for a re-calibration condition
if (CAPT_testForRecalibration(&keypadSensor) == true)
{
// If a re-calibration is required, perform it now
CAPT_calibrateSensor(&keypadSensor);
}
// If communications are enabled, transmit data now
#if ((CAPT_INTERFACE==__CAPT_UART_INTERFACE__)||\
(CAPT_INTERFACE==__CAPT_BULKI2C_INTERFACE__))
CAPT_writeElementData(0);
CAPT_writeSensorData(0);
#endif

Update a Sensor's Raw Data Only

For certain custom applications it may be desirable to only update a sensor's raw data after a conversion, bypassing all of the high-level processing. For applications that require this, the CAPT_updateSensorRawCount() may be used directly. This function only updates raw count values for each element in the sensor. No processing is performed on the data, and the sensor callback function is not called upon completion of the update. The raw data update function takes two additional parameters that specify details about type of conversion. An example function call is shown below that updates the raw data for a sensor named keypad. For details on the conversion type and oversampling type parameters, see the CAPT_updateSensorRawCount overview.

CAPT_updateSensorRawCount(
&keypadSensor, // Pointer to the sensor to update
eStandard, // Conversion type
eNoOversampling, // Oversampling type
LPM0_bits // Low power mode to use
);

After the update is complete, results may be looked up in each element's data structure. The following values are updated by this function:

  • Composite Raw Count (The raw result after the specied multi-frequency processing or oversampling)
  • Previous Composite Raw Count (The composite raw result from the previous sample)
  • Noise Level (if the conversion was a multi-frequency conversion, the spread between data at frequencies)
  • Raw Count for Each Frequency (if the conversion was a multi-frequency conversion, else a single frequency)

Below is an example of measuring a sensor named keypad without frequency hopping and with an oversampling level of 2. The composite output is read and used to perform some unknown task.

extern tElement keypadSensor_E00;
uint16_t rawSample;
// Perform the update
CAPT_updateSensorRawCount(
&keypadSensor, // Pointer to the sensor to update
eStandard, // Conversion type
e2xOversampling, // Oversampling type
LPM0_bits // Low power mode to use
);
// Read out the data
rawSample = keypadSensor_E00.ui16CompositeRawCount;
// Do Something ...

Enable an IO as a Shield

In self-capacitance mode, it is possible to enable an extra CapTIvate™ sensing IO to fire in phase with other IOs during a conversion. This provides a shielding effect that can reduce the parasitic capacitance of the sensing IOs.

In order to realize a shield IO, the following must be true:

  1. The sensor of interest must be a self-capacitance sensor
  2. The shield must be connected to an IO on a CapTIvate™ block that is not shared with the sensor of interest. For example, if the sensor of interest is a proximity sensor on CAP0.0 (block 0, pin 0), the shield may be connected to any pin on CAP1.x, CAP2.x, or CAP3.x- but it may NOT be connected to any pin on CAP0.x, since the proximity sensor (the sensor of interest) is on that block. If a shield is enabled on the same block as the sensor of interest, the effect will be equivalent to a digital "OR" in which the shield and the sensor of interest appear to be connected.

To enable an IO as a shield, use the library function CAPT_enableShieldIO() as shown below. This enables CAP1.0 to be a shield structure.

// Enable shield IO before calibration:
CAPT_enableShieldIO(1, 0);
// If needed, the shield can be disabled:
CAPT_disableShieldIO(1, 0);

Create a Custom EMC Configuration

When noise immunity is enabled for a design, or when any of the *WithEMC() function calls are used, the EMC processing plug-ins from the ADVANCED module are applied. The EMC processing plug-ins are configured via a data structure. A default configuration is provided in the user configuration file that works well for most applications. However, if some customization is needed the default structure in the user configuration file may be overridden and a new structure may be provided in the application.

While it is possible to directly edit the structure in the user configuration file, it is best to make the changes elsewhere as the user configuration file is auto-generated by the CapTIvate™ Design Center, and any changes will be lost when an update is performed.

To add a custom EMC configuration, create a new EMC configuration data structure by copying the structure from the user configuration file and placing it in the application. It must be re-named with a unique name. The new structure may be placed in the CAPT_App.c file, if desired. Below is an example:

const tEMCConfig myCustomEMCConfig =
{
// Conversion Style
.selfModeConversionStyle = eMultiFrequency,
.projModeConversionStyle = eMultiFrequencyWithOutlierRemoval,
// Oversampling Style
.selfModeOversamplingStyle = eNoOversampling,
.projModeOversamplingStyle = eNoOversampling,
// Jitter Filter Enable
.bJitterFilterEnable = true,
// Noise Thresholds and Calibration Noise Limits
.ui8NoiseThreshold = 20,
.ui16CalibrationNoiseLimit = 10,
.ui8CalibrationTestSampleSize = 8,
// Dynamic Threshold Adjustment Parameters
.bEnableDynamicThresholdAdjustment = true,
.ui8MaxRelThreshAdj = 76,
.ui8NoiseLevelFilterEntryThresh = 40,
.ui8NoiseLevelFilterExitThresh = 0,
.ui8NoiseLevelFilterDown = 6,
.ui8NoiseLevelFilterUp = 1,
.coeffA = _IQ31(0.0065),
.coeffB = _IQ31(0.050)
};

The structure is not modified at runtime, and thus may be declared as a const object.

In the the CAPT_appStart() function, there is a call to CAPT_loadEMCConfig(). Replace the passed configuration structure with the custom configuration structure.

Original

//
// Load the EMC configuration, if this design has
// noise immunity features enabled. This function call
// associates an EMC configuration with the EMC module.
//
#if (CAPT_CONDUCTED_NOISE_IMMUNITY_ENABLE==true)
CAPT_loadEMCConfig(&g_EMCConfig);
#endif

Modified

//
// Load the EMC configuration, if this design has
// noise immunity features enabled. This function call
// associates an EMC configuration with the EMC module.
//
#if (CAPT_CONDUCTED_NOISE_IMMUNITY_ENABLE==true)
CAPT_loadEMCConfig(&myCustomEMCConfig);
#endif

Now, the custom configuration may be modified to suite the needs of the application. For details on how to set the parameters in the EMC configuration structure, see the EMC Module section.

Stream Unformatted Data to the Design Center GUI

In addition to the element and sensor data streaming to the CapTIvate™ Design Center customizer windows, a mechanism exists to stream miscellaneous user-defined data to a oscilloscope plot with logging capability.

  • The data format is 16-bit unsigned integers.
  • Up to 29 values may be streamed.

To stream data, insert a call to the CAPT_writeGeneralPurposeData() function.

The function expects a pointer to an array of 16-bit unsigned integers, and a length value that specifies how many values there are, up to the maximum of 29.

#define BUFFER_SIZE (8)
uint16_t buffer[BUFFER_SIZE];
// Transmit the packet via the general purpose data mechanism
CAPT_writeGeneralPurposeData(&buffer[0], BUFFER_SIZE);

This mechanism is very helpful during development of noise immunity applications, as it allows for streaming of raw data and multi-frequency data. The code snippet below may be registered as a callback function for a sensor. It streams the data of the first element in the sensor with the following format:

  1. LTA
  2. Filtered Count
  3. Abs Thresh
  4. Noise Level
  5. F0 Raw Value
  6. F1 Raw Value
  7. F2 Raw Value
  8. F3 Raw Value
#define NOISETEST_PACKET_SIZE (8)
void NoiseTest_callbackHandler(tSensor* pSensor)
{
static uint16_t packet[NOISETEST_PACKET_SIZE];
tElement *element;
uint16_t threshold;
uint8_t i;
uint8_t j;
//
// Initialize variables
//
i = 0;
element = pSensor->pCycle[0]->pElements[0];
//
// Compute the touch threshold
//
threshold = element->ui8TouchThreshold;
if (pSensor->SensingMethod == eSelf)
{
threshold += CAPT_computeRelNoiseComp();
if (threshold > CAPT_getMaxRelThreshold())
{
threshold = CAPT_getMaxRelThreshold();
}
}
threshold = CAPT_convertRelToAbs(element->LTA.ui16Natural, threshold);
if (pSensor->DirectionOfInterest == eDOIDown)
{
threshold = element->LTA.ui16Natural - threshold;
}
else
{
threshold = element->LTA.ui16Natural + threshold;
}
//
// Frame the packet
//
packet[i++] = element->LTA.ui16Natural;
packet[i++] = element->filterCount.ui16Natural;
packet[i++] = threshold;
packet[i++] = element->ui16NoiseCount;
for (j=0; j<4; j++)
{
packet[i++] = element->pRawCount[j];
}
//
// Transmit the packet via the general purpose data mechanism
//
CAPT_writeGeneralPurposeData(&packet[0], NOISETEST_PACKET_SIZE);
}

When communications are enabled, this data will appear in the CapTIvate™ Design Center's user data plot. To view the streaming data, add a user data log bean, as shown below:

lib_howto_userdataplot.png
User Data Plot

Technical Details

The CapTIvate™ Software Library has certain technical features and requirements, described below.

Devices with CapTIvateā„¢ Software in ROM

Some MSP devices have portions of the CapTIvate™ Software Library in ROM, reducing the amount of FRAM or flash memory required for an application. Refer to the device data sheet to determine if a given device has library components in ROM.

Calling Functions in ROM

Calling library functions directly from ROM requires the use of two header files that are provided with the library, rom_captivate.h and rom_map_captivate.h. These header files provide a mapping between functions in the software library archive and the same function located in a device's ROM image. Use the function call technique described in the following example to simplify the procedure.

Example:

Assume the captivate.lib has been added to the project and header files rom_captivate.h and map_rom_captivate.h are included in the source file where the library function call will be made. To call the library function "foo()", it is recommended to make an implicit ROM call MAP_foo(). The complier parses through the rom_map_captivate.h file to determine if the function resides in ROM, and if it does, will make the ROM call. If this function does not exist in ROM then the compiler will make a call to the pre-compiled library version.

It is possible to explicitly call the function "foo()" from the pre-compiled library using foo(). The linker will pull this function from the pre-compiled library during the link process, adding the function to FRAM program memory. To explicitly call the function "foo()" from ROM, use ROM_foo(). This will force the compiler to make a call to ROM, with no impact on FRAM program memory.

MSP430 CPUX Memory Model

Smaller memory map devices with the TI MSP430 CPUX core (such as the MSP430FR26xx and MSP430FR25xx devices) have a total memory map that is less than 64kB. Such a memory map is accessible in its entirety with 16 bit pointers. In order to improve execution speed and reduce memory requirements, the ROM functions and pre-compiled library functions are compiled using the small code small data (SCSD) memory model. All CapTIvate™ software library projects must be compiled using the small code small data memory model in order to be compatible with the ROM functions and the pre-compiled library. Using the incorrect memory model will result in a linker error.


Base Module

The base module implements the core of the CapTIvate™ Software Library. It is responsible for providing the base feature set for initializing, calibrating, measuring and processing capacitive sensors.

The base module contains the following components:

  • Hardware Abstraction Layer (HAL)
  • Touch Layer
  • Interrupt Service Routine (ISR)
  • CapTIvate™ Software Library Type Definitions

HAL

The hardware abstraction layer provides access to the CapTIvate™ peripheral. This includes functions for performing the following tasks:

  • Configuring the CapTIvate™ peripheral periodic timer
  • Handling CapTIvate™ peripheral interrupts
  • Configuring IO for CapTIvate™
  • Configuring conversion settings
  • Loading and storing conversion results

Touch

The touch layer sits on top of the HAL layer and provides the sensor update routines, calibration algorithms, and basic sensor processing algorithms.

Sensor Update Routines

Several sensor update routines are available in the touch module:

  1. CAPT_updateSensor()
  2. CAPT_updateSensorWithEMC()
  3. CAPT_updateSensorRawCount()

CAPT_updateSensor()

This is the standard, fundamental sensor update routine that is used in most applications. Calling this function will immediately measure all of the elements within the sensor, and perform all of the standard signal processing, including:

  1. Applying any IIR count filtering as configured in the sensor structure. This function utilizes the hardware accelerated IIR filtering in the CapTIvate™ finite state machine.
  2. Testing of touch, proximity, and negative touch thresholds. This function utilizes the detection capability of the CapTIvate™ finite state machine to detect proximity and negative touch events.
  3. Processing of the long term average (LTA) as configured in the sensor structure. This function utilizes the hardware accelerated IIR filtering in the CapTIvate™ finite state machine to process the LTA.
  4. Application of touch/proximity de-bounce as configured in the sensor structure.
  5. Testing for re-calibration, if so configured in the sensor structure.

The function takes two parameters:

  • A pointer to the sensor structure to update
  • The low power mode to use during the conversion
CAPT_updateSensor(&mySensor, LPM3_bits);
Low Power Mode

The low power mode may be LPM0 to LPM3. The function returns when all measurements and processing are complete.

CAPT_updateSensorWithEMC()

This function is identical to CAPT_updateSensor(), with the addition of EMC processing components from the EMC module. See the EMC module for more details.

CAPT_updateSensorRawCount()

This is a basic function that may be used if access to the raw conversion data if that is all that is desired. Using this function bypasses all of the higher-level processing. Filtered count, long term average, and status parameters are not maintained. Only the raw results are populated. However, the function does allow for some low-level signal processing algorithms to be applied.

The function takes 4 parameters:

  • A pointer to the sensor structure to update
  • A conversion style specification
  • An oversampling specification
  • The low power mode to use during the conversion
CAPT_updateSensorRawCount(
&keypadSensor, // Pointer to the sensor to update
eStandard, // Conversion type
eNoOversampling, // Oversampling type
LPM0_bits // Low power mode to use
);
Conversion Style

The conversion style control influences the type of conversion used to update the raw values. The output is stored in the *.ui16CompositeRawCount* parameter of each element.

OptiontRawConversionStyle Enumeration
StandardeStandard
Multi-FrequencyeMultiFrequency
Multi-Frequency with Outlier RemovaleMultiFrequencyWithOutlierRemoval
  • Standard conversion, in which each time cycle is measured once. This is the normal conversion style that should be used for most applications.
  • Multi-frequency conversion, in which each time cycle is measured at 4 frequencies and the composite result is the average of the 4 frequencies.
  • Multi-frequency conversion with outlier removal, in which each time cycle is measured at 4 frequencies and the composite result is the average of the 4 frequencies after the largest outlier is removed from the data set. This is useful for mutual capacitance sensors that require conducted noise immunity.
Oversampling

The oversampling style control allows for the addition of oversampling in binary steps to a conversion. The available options are listed below:

OptiontOversamplingStyle Enumeration
No OversamplingeNoOversampling
Doublee2xOversampling
Quadruplee4xOversampling
8x e8xOversampling
16x e16xOversampling
32x e32xOversampling

When no oversampling is applied, each time cycle is sampled once and that value is used as the conversion result. When a level of oversampling is applied, each time cycle is sampled to the level of oversampling, and the results are averaged. This enables a basic averaging filter that helps with transient noise rejection.

Note that the measurement time increases 2x with each step.

Low Power Mode

The low power mode may be LPM0 to LPM3. The function returns when all measurements and processing are complete.

Calibration Algorithms

Sensors are calibrated with a top-level calibration call to CAPT_calibrateSensor() or CAPT_calibrateSensorWithEMC(). These two functions in turn call the two low-level calibration routines:

  • CAPT_calibrateGain(), for establishing each element's coarse gain and fine gain based upon the ui16ConversionGain** parameter of the sensor being calibrated
  • CAPT_calibrateOffset(), for establishing each element's offset subtraction based upon the previously calibrated coarse gain and fine gain values, and the sensor's *ui16ConversionCount parameter.

Functionally, calibration process is a two step process:

  1. First, CAPT_calibrateGain() is called. The coarse gain and fine gain is adjusted for each element in the sensor until the conversion result for each element is as close to the specified ui16ConversionGain parameter as possible.
  2. Second, CAPT_calibrateOffset() is called. The offset subtraction is increased until the conversion result for each element is as close to the specified ui16ConversionCount parameter as possible.

At the end of the calibration process, all elements should have conversion results that are normalized to the ui16ConversionCount parameter.

ISR

The CapTIvate™ peripheral has a single interrupt vector with 5 possible interrupt sources. For details on the interrupts themselves, see the auxiliary digital functions section of the technology chapter.

The software library uses the peripheral interrupts to set global status flags. The ISR is designed to quickly determine the cause of an interrupt, set the appropriate status flag, and exit. Upon exit, any low power mode is cleared so that the CPU remains alive after the interrupt. This is the mechanism that is used to wake up the application.

Of the 5 interrupts that are available, 2 are used solely by the library and 3 are left up to the application.

The following flags are used by the library, and generally do not need to be tested in the application:

  1. End of Conversion Interrupt (CAPT_END_OF_CONVERSION_INTERRUPT, CAPT_IV_END_OF_CONVERSION). This interrupt is triggered when a time cycle conversion is complete. The ISR sets the g_bEndOfConversionFlag, which signals the library that the conversion is complete.
  2. Max Count Error Interrupt (CAPT_MAX_COUNT_ERROR_INTERRUPT, CAPT_IV_MAX_COUNT_ERROR). This interrupt is triggered if a conversion exceeds the error threshold that was specified for a sensor. The ISR sets the g_bMaxCountErrorFlag, which signals the library that the error limit was reached and the conversion has stopped.

The following flags are meant to be used by the application:

  1. Conversion Timer Interrupt (CAPT_TIMER_INTERRUPT, CAPT_IV_TIMER). This interrupt is triggered when the CapTIvate™ interval timer has counted up to the compare register, indicating that it is time to trigger a conversion. The ISR sets the g_bConvTimerFlag. It is expected that the application is configuring and monitoring this interrupt.
  2. Conversion Counter Interrupt (CAPT_CONVERSION_COUNTER_INTERRUPT, CAPT_IV_CONVERSION_COUNTER). This interrupt is triggered when conversion counter has reached the conversion counter interrupt threshold. This mechanism allows for an interrupt to be thrown after a certain number of conversions have taken place. In a wake-on-proximity application, this can be used to periodically wake up the CPU to ensure that the application is proceeding as expected and that all values are within range. This interrupt is enabled by the CAPT_startWakeOnProxMode() function, and is disabled by the CAPT_stopWakeOnProxMode() function.
  3. Detection Interrupt (CAPT_DETECTION_INTERRUPT, CAPT_IV_DETECTION). This interrupt is triggered when any element in a time cycle has its proximity or negative touch thresholds exceeded at the end of a conversion. This mechanism allows for the CPU to wake up due to a threshold crossing. This interrupt is enabled by the CAPT_startWakeOnProxMode() function, and is disabled by the CAPT_stopWakeOnProxMode() function.

Type Definitions

The type definitions file, CAPT_Type.h, contains the definitions for all of the data structures that are used in the library. It is important that the data structures are not modified! The library functions in ROM as well as the pre-compiled library are dependent upon the data structure configuration being consistent.


Advanced Module

The advanced module serves two main purposes: It provides processing plug-ins to the base module, and it provides the top level API. The top level API is implemented by the manager module. Processing plug-ins include button processing, slider/wheel processing, and EMC processing.

Manager

The manager provides the top level API for the library. For details on how to use the top level API, see the How to Use the Top Level API section.

Buttons

The buttons processing plug-in is a dominant element computation. The dominant element computation compares the delta response from all elements within the sensor. The element with the highest delta response is reported as the dominant element. For details on how to use the dominant element feature, see the How to Access the Dominant Button section.

Sliders and Wheels

The slider processing plug-in provides a vector position computation to determine the location of a touch over a 1-dimensional array of elements. The same vector math is utilized for processing slider and wheel sensors. The slider is really just a special case of a wheel where the endpoints are disconnected.

Supported Sizes

A slider or wheel sensor must be composed of at least 3 elements, but no more than 12 elements.

Supported Resolution

The algorithm allows for up to 16 bits of resolution, although 5-10 bits is the typical use-case. Measurement results will be reported back from 0 to resolution-1.

Slider Endpoint Trim

The slider algorithm allows for "endpoint trim" to ensure that the beginning position is true 0 and the end position is the resolution-1. For details on how the endpoint trim works, see the trim help section.

EMC

The CapTIvate™ Software Library includes an EMC module in the advanced module. This module provides processing plug-ins to the touch layer to enhance robustness in the presence of noise. This section discusses how to configure that module. For a detailed noise immunity design guide, visit the noise immunity section of the design chapter.

EMC Module Background

While the CapTIvate™ peripheral provides a significant feature set for dealing with electromagnetic compatibility issues on its own, some amount of digital signal processing is still required to process the raw data into usable values. The EMC module in the CapTIvate™ Software Library fills that need by providing configurable algorithms to the base touch layer of the software library. It is not necessary to call EMC processing functions directly; rather, they are automatically called by the touch layer. The various EMC features are enabled, disabled, and configured via an EMC configuration structure.

The EMC module provides the following feature set:

  1. Multi Frequency Processing (MFP) Algorithm for resolving a raw measurement set of 4 frequencies into a single, usable measurement result
  2. Multi Frequency Calibration Algorithm for ensuring that accurate, usable calibration values are obtained during the sensor calibration process, even in a noisy environment
  3. Oversampling (Averaging) Filter to improve SNR
  4. Jitter Filter to remove small 1-count glitches
  5. Global Relative Noise Level Tracking to keep a global value of the noise level observed on all self-capacitance elements in the system for use in dynamic threshold adjustment
  6. Dynamic Threshold Adjustment (DTA) Algorithm for calculating a threshold adjustment factor to compensate for increased sensitivity of self-capacitance sensors in the presence of noise

Using the EMC Module

When using the CapTIvate™ Software Library, the EMC module functions are not called by the application. Rather, noise immunity is enabled for a user configuration at a top level. When noise immunity is enabled in the library for a design, the top level library functions for calibration and sensor measurement are replaced with EMC versions of the same functions.

Enabling Noise Immunity (EMC) Features

It is best to enable noise immunity via the CapTIvate™ Design Center. The controller customizer has a compile-time option for noise immunity. Selecting this option sets the CAPT_CONDUCTED_NOISE_IMMUNITY_ENABLE compile-time definition in the CAPT_UserConfig.h file to true when source code is generated.

Function Replacements with Noise Immunity Enabled

When the compile-time option is set, the manager layer will make calls to EMC versions of functions rather than the standard versions. Below is a mapping of which functions are replaced:

Top Level Functions
DescriptionStandard FunctionEMC Function
Calibrate a SensorCAPT_calibrateSensor()CAPT_calibrateSensorWithEMC()
Update a SensorCAPT_updateSensor()CAPT_updateSensorWithEMC()

These top level functions are called by the application via abstractions in CAPT_Manager.

Supporting Functions
DescriptionStandard FunctionEMC Function
Process a CycleCAPT_processFSMCycle()CAPT_processCycleWithEMC()
Update Prox/Touch StatusCAPT_updateProx, CAPT_updateTouchCAPT_updateSelfElementProxTouchWithEMC(), CAPT_updateProjElementProxTouchWithEMC()

These functions are called inside the touch layer, and are not directly called by the application.

EMC Module Configuration

The EMC Module is configured through the tEMCConfig structure. The EMC layer only reads from this structure, so the configuration may be kept in non-volatile read-only memory if desired. A default configuration is provided in the user configuration file, and is shown below:

const tEMCConfig g_EMCConfig =
{
// Conversion Style
.selfModeConversionStyle = eMultiFrequency,
.projModeConversionStyle = eMultiFrequencyWithOutlierRemoval,
// Oversampling Style
.selfModeOversamplingStyle = eNoOversampling,
.projModeOversamplingStyle = eNoOversampling,
// Jitter Filter Enable
.bJitterFilterEnable = true,
// Noise Thresholds and Calibration Noise Limits
.ui8NoiseThreshold = 20,
.ui16CalibrationNoiseLimit = 10,
.ui8CalibrationTestSampleSize = 8,
// Dynamic Threshold Adjustment Parameters
.bEnableDynamicThresholdAdjustment = true,
.ui8MaxRelThreshAdj = 76,
.ui8NoiseLevelFilterEntryThresh = 40,
.ui8NoiseLevelFilterExitThresh = 0,
.ui8NoiseLevelFilterDown = 6,
.ui8NoiseLevelFilterUp = 1,
.coeffA = _IQ31(0.0065),
.coeffB = _IQ31(0.050)
};

The default values were selected by bench characterization and have proven effective for several different sensing panels. However, for certain applications and/or certain noise environments, it may be necessary to adjust some of the parameters. To implement a custom configuration, simply create a new tEMCConfig structure with the desired values, and pass it's address to CAPT_loadEMCConfig() when the application is initialized at start-up. Note that the CapTIvate™ starter project makes this call in CAPT_appStart() just before CAPT_calibrateUI().

CAPT_loadEMCConfig(&g_EMCConfig);

The configuration parameters can be grouped into 5 different categories:

  • Conversion Style Control
  • Oversampling Style Control
  • Jitter Filter Control
  • Noise Thresholds and Calibration Noise Limits
  • Dynamic Threshold Adjustment Parameters

Each group will be discussed in detail below.

Conversion Style Control

// Conversion Style
.selfModeConversionStyle = eMultiFrequency,
.projModeConversionStyle = eMultiFrequencyWithOutlierRemoval,

The conversion style control influences the type of conversion used by EMC sensor update functions. Conversion style is specified separately for self and mutual capacitance sensors, enabling different algorithms to be applied to designs that have both self and mutual sensors. There are three possible conversion styles:

  • Standard conversion, in which each time cycle is measured once.
  • Multi-frequency conversion, in which each time cycle is measured at 4 frequencies and the composite result is the average of the 4 frequencies
  • Multi-frequency conversion with outlier removal, in which each time cycle is measured at 4 frequencies and the composite result is the average of the 4 frequencies after the largest outlier is removed from the data set.

Conversion style is a data type that may be passed to the CAPT_updateSensorRawCount() function, which is what the EMC sensor update functions use to measure sensors. The enumeration options are shown below.

OptiontRawConversionStyle Enumeration
StandardeStandard
Multi-FrequencyeMultiFrequency
Multi-Frequency with Outlier RemovaleMultiFrequencyWithOutlierRemoval

For mutual (projected) capacitance sensors, the recommended style is multi-frequency with outlier removal. The narrow-band susceptibility of mutual capacitance sensors suites them well to this approach. If noise exists at one of the conversion frequencies, that outlying sample is removed and the composite result is re-calculated with the remaining values.

For self-capacitance sensors, the recommended style is multi-frequency if a low-value series impedance is used (<50k-ohm), and standard if a high-value series impedance is used (>50k-ohm). For the higher series impedance approach, a hardware low-pass filter is formed by the electrode/pin capacitance and the series impedance, attenuating noise. For the low-value series impedance approach, a multi-frequency conversion provides 4 data points from which a spread can be calculated and use as a noise level reference. That reference can then be used as an input to the dynamic threshold adjustment algorithm.

Oversampling Style Control

// Oversampling Style
.selfModeOversamplingStyle = eNoOversampling,
.projModeOversamplingStyle = eNoOversampling,

The oversampling style control allows for the addition of oversampling in binary steps to a conversion. Oversampling style is a data type that may be passed to the CAPT_updateSensorRawCount() function, which is what the EMC sensor update functions use to measure sensors.

OptiontOversamplingStyle Enumeration
No OversamplingeNoOversampling
Doublee2xOversampling
Quadruplee4xOversampling
8x e8xOversampling
16x e16xOversampling
32x e32xOversampling

When no oversampling is applied, each time cycle is sampled once and that value is used as the conversion result. When a level of oversampling is applied, each time cycle is sampled to the level of oversampling, and the results are averaged. This enables a basic averaging filter that helps with transient noise rejection.

Note that this oversampling is in addition to the multi-frequency scanning. For example, a mutual capacitance sensor with a multi-frequency conversion style and a 4x oversampling style is actually measured 16 times per update- 4 frequencies per sample, and a 4x oversample.

For designs that have a smaller number of buttons, more oversampling can be applied, which improves the overall SNR. Oversampling style is specified separately for self and mutual capacitance sensors.

Jitter Filter Control

// Jitter Filter Enable
.bJitterFilterEnable = true,

A basic 1-level jitter filter may be applied when sensors are updated with EMC features enabled. The jitter filter has a simple control (on or off). The filter looks at each new sample and determines if it is greater or less than the previous sample. If it is greater, the new sample is decremented by a value of 1. if it is less, the new sample is incremented by 1. This reduces low-level jitter in measurements, improving SNR. It is recommended that the jitter filter be enabled in most applications.

Noise Level Thresholds and Calibration Noise Limits

// Noise Thresholds and Calibration Noise Limits
.ui8NoiseThreshold = 20,
.ui16CalibrationNoiseLimit = 10,
.ui8CalibrationTestSampleSize = 8,

These parameters primarily exist to enable alerting of the application to the fact that the amount of noise observed in the system is greater than a specified amount. In addition, they aid in calibration if noise is present during a calibration.

Noise Threshold

The ui8NoiseThreshold parameter specifies the relative noise level beyond which an element's noise detected status flag should be set. This provides a mechanism to alert the application that there is a certain amount of noise present in the measurement. The parameter is specified is a relative value, not an absolute value. The value is defined as a percentage of the long term average (LTA), in which 0=0% and 128=100%. The absolute noise threshold would be calculated as the relative threshold multiplied by the LTA and divided by 128, as shown below.

lib_advanced_rel_to_abs.png
Relative/Absolute Noise Threshold Conversion

Relative thresholds are used here so that they can be applied to an application that may have many sensors with different conversion count settings.

Note that this noise threshold only serves the purpose of setting the noise detected flag- it does not impact the processing of the library in any way.

Calibration in Noisy Environments

The ui16CalibrationNoiseLimit and ui8CalibrationTestSampleSize parameters are used to test the results of the calibration process when multi-frequency scanning is enabled. If the MCU powers up in an environment with noise at one of the conversion frequencies, it is possible that the calibration algorithm may produce invalid calibration values at that frequency. To determine if a calibration value may be corrupt, after the calibration process is complete each element is sampled ui8CalibrationTestSampleSize times. Out of that sample set, the peak-to-peak difference at each frequency in the set is compared with the ui16CalibrationNoiseLimit. If the peak-to-peak variation of the measurement results at a given conversion frequency is greater than the ui16CalibrationNoiseLimit parameter, that frequency's calibration values are marked as invalid. When this happens, they are replaced with the calibration values of the nearest valid conversion frequency.

There are 3 possible outcomes from the multi-frequency calibration test:

  1. All 4 conversion frequencies provided data that was noise-free. Thus, they retain their original calibration values.
  2. 1 to 3 conversion frequencies provided data that was noisy. the noisy frequencies have their calibration values replaced with the values from the nearest conversion frequency with valid, noise-free data.
  3. All 4 conversion frequencies provided data that was noisy. In this scenario, there is no way to determine if the calibration values are valid or not. To alert the application, the sensor's bCalibrationError status flag and bSensorNoiseState status flag are set. Applications should test these flags after the calibration process to determine if a usable calibration solution was obtained. If a valid calibration solution was not obtained, the application should stall and re-attempt the calibration process until a valid calibration is obtained.

Dynamic Threshold Adjustment Parameters

// Dynamic Threshold Adjustment Parameters
.bEnableDynamicThresholdAdjustment = true,
.ui8MaxRelThreshAdj = 76,
.ui8NoiseLevelFilterEntryThresh = 40,
.ui8NoiseLevelFilterExitThresh = 0,
.ui8NoiseLevelFilterDown = 6,
.ui8NoiseLevelFilterUp = 1,
.coeffA = _IQ31(0.0065),
.coeffB = _IQ31(0.050)

The dynamic threshold adjustment (DTA) parameters enable and configure the DTA algorithm. The parameters are introduced below:

MemberDescriptionDefault ValueValid Values
bEnableDynamicThresholdAdjustmentEnable or disable dynamic threshold adjustment. Note that dynamic threshold adjustment only applies to self capacitance sensors in either case.truetrue, false
ui8NoiseLevelFilterEntryThreshIf the noise level is increasing (low to high vector) and the new noise sample is below this value, the global and local value filters will be disabled to allow for rapid tracking. A value of '0' keeps the filters enabled at all times.320-128
ui8NoiseLevelFilterExitThreshIf the noise level is decreasing (high to low vector) and the new noise sample is below this value, the global and local value filters will be disabled to allow for rapid tracking. A value of '0' keeps the filters enabled at all times.00-128
ui8NoiseLevelFilterDown The filter beta applied to the global filtered noise value when the new noise sample is lower than the filtered noise value.50-15
ui8NoiseLevelFilterUp The filter beta applied to the global filtered noise value when the new noise sample is higher than the filtered noise value.10-15
coeffAThe 'A' coefficient in the dynamic threshold adjustment algorithm calculation.0.00650-0.999999999
coeffBThe 'B' coefficient in the dynamic threshold adjustment algorithm calculation.0.01000-0.999999999

The bEnableDynamicThresholdAdjustment parameter enables and disables the DTA algorithm. The DTA algorithm only applies to self capacitance sensors. See the DTA section for an overview of how the DTA algorithm works.

EMC Module Algorithms

Multi Frequency Processing (MFP) Algorithm

The multi frequency processing algorithm is implemented in the CAPT_resolveMultiFreqSet() function. When noise immunity is enabled, each element is measured at four different conversion frequencies to gather more data in the presence of noise. The algorithm is then applied to the four raw measurements. The output of the algorithm is a single, composite measurement and a noise level. The composite measurement is then used by the higher levels of the library just like a raw sample normally would. The noise level is used to update each element's filtered noise level.

Dynamic Threshold Adjustment (DTA) Algorithm

The dynamic threshold adjustment (DTA) algorithm is implemented in the CAPT_computeRelativeNoiseComp() function. The algorithm calculates threshold adjustments based on the amount of noise seen in a history of measurements. It relies on a filtered relative noise value as an input, and it calculates the corresponding threshold adjustment to be applied for proximity and touch detection. The threshold adjustment is calculated based on a polynomial model as shown below, where 'x' is the relative noise value and 'y' is the corresponding relative threshold adjustment. The polynomial allows for greater adjustment at higher noise levels.

lib_dta_formula.png
DTA Formula

This formula with the default values provides the adjustment curve shown below. A linear (A=0; B=0.5) curve is also shown for reference.

lib_dta_formula_plot.png
DTA Default Value Response Curve

Communications Module

The CapTIvate™ Touch Library includes a communications module for connecting CapTIvate™ MCUs to the outside world. This section discusses the architecture, features, and specification for that communications module, as well as how it may be used in a variety of applications from development to production.

This section assumes that the reader is familiar with the following:

  • Capacitive sensing as a user interface technology
  • The Captivate ecosystem and touch library
  • Basic MSP microcontroller architecture

Background

Designing a capacitive touch interface is an iterative process. Every sensor in the system must be individually tuned and optimized to achieve the desired sensitivity and "feel." Having the ability to communicate in real-time between a PC GUI and the target MCU drastically reduces the amount of time needed to tune an interface, and can provide better tuning results as the features incorporated into the CapTIvate™ peripheral can quickly and easily be exercised to determine the best configuration.

Following the design and tuning phase, a capacitive touch microcontroller takes on one of two roles in a system. It may be a dedicated human-machine interface (HMI) that only serves to resolve a capacitive touch panel into usable information (like touch/no touch, or a slider position), or it may double as a host processor, integrating other functionality such as monitoring sensors or controlling other functions in the system. In the first case (the dedicated HMI case), the controller will almost always require a way to communicate the status of the interface to some other host, which may be another MCU or an MPU. This interface could be as simple as a GPIO that gets set when a button is pressed, or as complex as a full I2C protocol with addressable parameters.

Overview

The CapTIvate™ Software Library communications module provides a single solution to the two needs above. The communications module is a layered set of firmware with a simple top-level API, designed to link a CapTIvate™ MCU to the CapTIvate™ Design Center PC GUI or to a host processor of some kind via a standard, common serial interface.

In a capacitive touch application the MCU is responsible for measuring capacitive sensors, processing the measurement to interpret some kind of result, and transmitting that result either to the application locally or to a host processor. The communications layer provides the "transmission" part of the equation. The diagram below portrays how the communications module fits in with the rest of the firmware in a typical CapTIvate™ application.

The CapTIvate™ communications module is layered and contains several standalone features that are interlinked together. These features are introduced below. To use the communications module, it is only necessary to set up the configuration file and call the top-level APIs.

The communications module contains 4 layers. In order of decreasing abstraction, they are:

  • Interface Layer [COMM/CAPT_Interface.c/.h]
    • The interface layer implements the top-level API.
  • Protocol Layer [COMM/CAPT_Protocol.c/.h]
    • The protocol layer implements Captivate protocol packet generation and packet interpretation.
  • Serial Driver Layer [COMM/Serial_Drivers/*.c/.h]
    • The serial driver layer contains several interchangeable serial driver options.
    • One and only one option may be selected at any given time.
    • Selection and configuration is handled in the configuration file.
    • The serial drivers are built on top of the MSP430 DriverLib API.
    • UART and I2C Slave drivers are provided.
  • Data Structure Layer [COMM/CAPT_ByteQueue.c/.h, COMM/CAPT_PingPongBuffer.c/.h]
    • The data structure layer implements basic abstract data types, such as a FIFO queue and a ping pong buffer to aid serial communication.

Interface Layer

The CapTIvate™ interface layer is the top-level communication layer. It provides the top-level function calls that are used by the application, and serves to marry together the protocol layer (which handles packet generation and interpretation) with the serial driver (which actually moves the data in and out of the microcontroller). The functionality provided is covered in this section below. All application access to the communications module should be through the interface layer.

Using the Communications Module: Initializing the Interface

The communications module must be initialized at startup by the application via a call to CAPT_initCommInterface(). This top-level init function handles opening the selected serial driver, as well as initializing any queues/buffers that are needed for communication.

DescriptionDeclaration
Init the Communications Moduleextern void CAPT_initCommInterface(tCaptivateApplication *pApp)

Using the Communications Module: Handling Incoming Data

Incoming raw data from a host is buffered by the serial driver to be serviced when the application is available to do so. The application must periodically call CAPT_checkForInboundPacket() to check to see if any packets have been received from the host. This top-level function will check for packets in the receive queue of the serial driver, and if any packets are found, they will be processed according to their type. Typically, this function is called in a background loop when the application is available. Note that the serial drivers will exit active from sleep if a data is arriving from the host, which can be used as a mechanism to wake up the background loop to call this function.

The CAPT_checkForRecalibrationRequest() function should also be called periodically to see if any of the packets received and handled by CAPT_checkForInboundPacket() require the application to re-calibrate the user interface. An example of this would be if a packet was received that changed the conversion count, requiring a re-calibration of the sensors in the system.

DescriptionDeclaration
Check for an Inbound Packet, and Process Itextern bool CAPT_checkForInboundPacket(void)
Check for a Re-calibration Requestextern bool CAPT_checkForRecalibrationRequest(void)

Using the Communications Module: Writing Out Data

The interface layer provides 3 top-level constructs for transmitting data to the host. The three functions below handle generation of the appropriate packet, management of the transmit ping/pong buffers, and transmission over the serial interface. If the serial peripheral is available (not busy) these calls are non-blocking, and the packets that are generated are transmitted to the host via interrupt service routines in the serial driver.

DescriptionDeclaration
Write Element Dataextern bool CAPT_writeElementData(uint8_t ui8SensorID)
Write Sensor Dataextern bool CAPT_writeSensorData(uint8_t ui8SensorID)
Write General Purpose Dataextern bool CAPT_writeGeneralPurposeData(uint16_t *pData, uint8_t ui8Cnt)

Compile-Time Configuration

The compile-time configuration options are set in the CAPT_CommConfig.h file. The available compile-time options are described below.

Interface Selection Definition

ParameterFileValid Values
CAPT_INTERFACECAPT_UserConfig.h__CAPT_NO_INTERFACE__, CAPT_UART_INTERFACE, CAPT_BULKI2C_INTERFACE, CAPT_REGISTERI2C_INTERFACE

CAPT_INTERFACE, unlike the remaining definitions, is located in the User Config file (CAPT_UserConfig.h). It selects the interface that the communications module should be built for. If the communication module should be excluded from the build, then CAPT_NO_INTERFACE should be set. Otherwise, the desired communication mode should be set.

NOTE: This value is automatically populated in the CAPT_UserConfig.h file by the Design Center during source code generation.

Transmit Buffer Size Definition

ParameterFileValid Values
CAPT_TRANSMIT_BUFFER_SIZECAPT_CommConfig.hUnsigned Integer

CAPT_TRANSMIT_BUFFER_SIZE defines the size of the transmit buffer. Note that 2x this size will be allocated, since ping-pong buffering is used. This size should also be at least 2x the size of the largest packet, to allow for byte stuffing.

Receive Queue Buffer Size Definition

ParameterFileValid Values
CAPT_QUEUE_BUFFER_SIZECAPT_CommConfig.hUnsigned Integer

CAPT_QUEUE_BUFFER_SIZE defines the size of the receive queue. This is the queue that the serial driver uses to buffer received data until the data is processed by a call to CAPT_checkForInboundPacket(). If it seems like packets are being dropped, a good first step is to increase the size of this buffer.

I2C Slave Serial Driver Receive Buffer Size Definition

ParameterFileValid Values
CAPT_I2C_RECEIVE_BUFFER_SIZECAPT_CommConfig.hUnsigned Integer

CAPT_I2C_RECEIVE_BUFFER_SIZE defines the size of the receive buffer used by the I2C Slave driver, if that driver is selected. This buffer size should be at least as large as the maximum length I2C bus write transaction that is expected.

I2C Slave Serial Driver Buffer Size in Register Mode Definition

ParameterFileValid Values
CAPT_I2C_REGISTER_RW_BUFFER_SIZECAPT_CommConfig.hUnsigned Integer

CAPT_I2C_REGISTER_RW_BUFFER_SIZE defines the size of the buffer used by the I2C Slave driver when the communication interface is configured in register I2C mode. This buffer size should be at least as large as the maximum length I2C bus transaction that is expected.

Protocol Layer

The CapTIvate™ protocol is a communications specification for sending capacitive touch specific data. It enables MSP430 Captivate-equipped microcontrollers to communicate with design, debug, and tuning tools on host PCs. In addition to this function, it can also provide a mechanism for interfacing a Captivate MCU to another host MCU or SoC in the context of a larger system. This guide discusses the details of the protocol itself: packet types and packet structure.

The Captivate protocol is a packet-based serial messaging protocol. It includes provisions for passing real-time capacitive measurement data from a Captivate target MCU to another processor, as well as provisions for tuning parameter read and write.

Use Cases

The various use cases for the protocol are described below.

  1. Capacitive Touch Development and Tuning Capacitive touch development and tuning involves looking at real-time data from a touch panel and adjusting software parameters to achieve the desired response and feel from the sensors on that panel. For example: tuning a capacitive button involves looking at the raw data coming back from the microcontroller about that button, and adjusting thresholds, de-bounce, and filters accordingly to create a robust user interface. Having the ability to adjust all of these software parameters in real-time while looking at sensor data, without re-compiling code, is extremely powerful and reduces development time. The Captivate protocol was designed with the Captivate Design Center specifically to meet this need.
  2. Interface to Host Processor Most capacitive touch user interfaces involve a dedicated microcontroller driving the touch panel, which communicates up to a host processor of some kind. The flexibility of the Captivate protocol allows for it to be re-used as an interface to a host processor; it can stream touch status, proximity status, and slider/wheel position up to a host.
  3. In-Field / In-System Debug Interface and Tuning Since the Captivate protocol supports reading and writing of capacitive touch tuning parameters, as well as the streaming of real-time data, it could potentially be utilized as a diagnostic tool in the field when coupled with the Captivate Design Center PC tool.

Introduction to Packet Types

The Captivate protocol supports five packet types: sensor packets, cycle packets, parameter packets, general purpose packets, and trackpad packets. In a capacitive touch system, there are two endpoints in the communication link: the target MCU itself, and the host. The host might be a PC tool or some kind of embedded processor. Sensor packets, cycle packets, general purpose packets, and trackpad packets carry information about the current state of the touch panel being driven by the target MCU. These packets are UNIDIRECTIONAL, and only travel from the target to the host. Parameter packets are BIDIRECTIONAL, and may travel from the target to the host or from the host to the target.

lib_comm_protocol_packet_direction.png
Packet Directionality

Sensor Packets

Sensor packets are unidirectional packets from the Captivate MCU to the host. They provide information about the current state of a sensor. Sensor state information includes things like dominant button, slider or wheel position, sensor global proximity state, and sensor global touch/previous touch state.

Cycle Packets

Cycle packets are unidirectional packets from the Captivate MCU to the host. They provide low level element information, such as element touch status, element proximity status, element count, and element long term average for all of the elements within a cycle. These packets are typically used in the tuning phase, where it is desirable to have real-time views of count and long term average for setting thresholds and tuning filters.

Trackpad Packets

Trackpad packets are unidirectional packets from trackpad MCUs to the host. They provide the X and Y coordinates of touches on the trackpad.

General Purpose Packets

General purpose packets are unidirectional packets from a Captivate MCU to the host. They serve as a generic container to send any information that can be formatted as a 16-bit unsigned integer. This channel can serve as a debug tool for sending any kind of information that doesn't fit into any of the other packet types. Up to 29 integers (58 bytes) may be sent in a single packet.

Parameter Packets

Parameter packets are bi-directional packets between a host and the Captivate MCU. Parameter packets allow for the host to adjust a tuning parameter on the target at runtime. For example, the touch threshold for an element or the resolution of a slider could be adjusted by sending the appropriate parameter command. Parameters can be read or written. Parameter reads and writes from a host to a target MCU always result in a read-back of the most current value (the value after the write, in the case of a write).

Transmission Rules

The packet types discussed above are transmitted via a serial interface of some kind between the target and the host. Full-duplex UART is the typical interface. To provide reliable and accurate packet transmission, a set of transmission rules is applied to all packets when being transmitted. These rules are discussed in the HID Bridge Packet Mode section.

The following functions aid in applying transmission rules:

DescriptionDeclaration
Stuff Sync Bytes in a Packetextern uint16_t CAPT_stuffSyncBytes(uint8_t *pBuffer, uint16_t ui16Length)
Verify a Checksumextern bool CAPT_verifyChecksum(const uint8_t *pBuffer, const uint16_t ui16Length, const uint16_t ui16Checksum)
Get a Checksumextern uint16_t CAPT_getChecksum(const uint8_t *pBuffer, const uint16_t ui16Length)
Identify and Frame a Packet in a Receive Data Queueextern bool CAPT_processReceivedData(tByteQueue *pReceiveQueue, tParameterPacket *pPacket, tTLProtocolProcessingVariables *pVariables)

Format: Sensor Packets

Sensor packets have a fixed length of 6 bytes. There are two control bytes and four data payload bytes.

lib_comm_protocol_sensorpacket.png
Sensor Packet Format
  1. Command Byte [0] The command byte for a sensor packet is always 00h.
  2. Sensor ID Byte [1] The sensor ID byte contains an unsigned 8-bit integer that specifies the ID of the sensor whose data is being transmitted. Sensor ID on the target side is typically established by the order of sensor pointers in the global sensor pointer array. Sensors are sorted alphabetically by the Captivate Design Center PC GUI.
  3. Data Payload Bytes [2-5] The data payload bytes contain the sensor data that is being sent. The information contained in the payload is dependent upon the sensor type. The table below describes the payload on a sensor type basis.
Sensor TypeByte 0Byte 1Byte 2Byte 3
Button GroupDominant Element (256 elements max)Previous Dominant Element (256 elements max)ReservedSensor Status
SliderSlider Position (Lower 8 bits of 16 bits)Slider Position (Upper 8 bits of 16 bits)ReservedSensor Status
WheelWheel Position (Lower 8 bits of 16 bits)Wheel Position (Upper 8 bits of 16 bits)ReservedSensor Status
ProximityReservedReservedReservedSensor Status
TrackpadReservedReservedReservedSensor Status

The sensor status byte, included in button group, slider, and wheel sensor packets, provides additional data about the state of the sensor that is often meaningful. The status flags are all boolean flags, and are assigned to bit positions as follows:

Bit Mask (Position)Sensor Status Flag
Bit 0 (01h)Global Sensor Touch Flag
Bit 1 (02h)Global Sensor Previous Touch Flag
Bit 2 (04h)Global Sensor Proximity Flag
Bit 3 (08h)Global Sensor Detect Flag (Prox Detect Pre-Debounce)
Bit 4 (10h)Global Sensor Negative Touch Flag (Reverse Touch)
Bit 5 (20h)Global Sensor Noise State
Bit 6 (40h)Global Sensor Max Count Error Flag
Bit 7 (80h)Global Sensor Calibration Error Flag

NOTE: For slider / wheel sensors, the 16 position bits in the data payload will contain the valid slider or wheel position when the global sensor touch flag is true on that sensor. If there is not a global touch detection, the 16 position bits will all be set high (0xFFFF for the slider/wheel position value).

NOTE: Reserved fields are still transmitted, but do not contain any meaningful data. Do not use or rely on any data transmitted in a reserved field.

Sensor packets are generated via a call into the protocol layer. The CAPT_getSensorPacket() function looks up the sensor at index ui8SensorID in the array sensorArray, and stores the generated packet in the buffer space pointed to by pBuffer. The length of the packet is returned by the function.

DescriptionDeclaration
Get a Sensor Packetextern uint16_t CAPT_getSensorPacket(tSensor **sensorArray, uint8_t ui8SensorID, uint8_t *pBuffer)

Format: Cycle Packets

Cycle packets have a variable length which is dependent upon the number of elements within the cycle. There are always 3 control bytes and 3 state bytes. In addition to those 6 bytes, there are 4 bytes per element in the cycle.

lib_comm_protocol_cyclepacket.png
Cycle Packet Format
  1. Command Byte [0] The command byte for a cycle packet is always 01h.
  2. Sensor ID Byte [1] The sensor ID byte contains an unsigned 8-bit integer that specifies the ID of the sensor whose data is being transmitted. Sensor ID on the target side is typically established by the order of sensor pointers in the global sensor pointer array. Sensors are sorted alphabetically by the Captivate Design Center PC GUI.
  3. Cycle ID Byte [2] The cycle ID byte contains an unsigned 8-bit integer that specifies the ID of the cycle whose data is being transmitted, relative to the sensor. For example, in a sensor with 3 cycles, the first cycle would have an ID of 0, the second, an ID of 1, and the third, an ID of 2. The cycle ID should correspond to the index of the cycle in the sensor's cycle pointer array.
  4. Cycle State Bytes [3-5] The cycle state bytes specify the touch and proximity detection flags for each element in the cycle. Up to 12 elements per cycle are supported. Bytes 3 through 5 of the cycle packet comprise a 24 bit cycle state section, where the lower 12 bits represent the proximity state for each element in a bitwise fashion, and the upper 12 bits represent the touch state for each element in a bitwise fashion.
  5. Element LTA and Element Count Bytes [6-n], n=5+4(number of elements) The remainder of the cycle packet is comprised of element LTA and count values. LTA and count are represented as 16 bit unsigned integers. As such, 32 bits are required for each element in the LTA/Count section. The packet shall increase in size in 32 bit (4 byte) increments for every additional element in the cycle, up to a maximum of 12 elements. The LTA is sent first, and the count second. Per the protocol, when sending values greater than one byte, the transmission sequence is lowest order byte to highest order byte.

Cycle packets are generated via a call into the protocol layer. The CAPT_getCyclePacket() function looks up the cycle at index ui8Cycle in the sensor at index ui8SensorID in the array sensorArray, and stores the generated packet in the buffer space pointed to by pBuffer. The length of the packet is returned by the function.

DescriptionDeclaration
Get a Cycle Packetextern uint16_t CAPT_getCyclePacket(tSensor **sensorArray, uint8_t ui8SensorID, uint8_t ui8Cycle, uint8_t *pBuffer);

Format: Trackpad Packets

Trackpad packets have a variable length which is dependent upon the maximum number of simultaneous touches supported by the trackpad device. There are 3 control bytes. Following the control bytes, there is a trackpad gesture byte for indicating the detection of a gesture and what the gesture was. For each simultaneous touch the trackpad device supports, 4 additional payload bytes are added to the packet. The packet format can be seen below.

lib_comm_protocol_trackpadpacket.png
Trackpad Packet Format
  1. Command Byte [0] The command byte for trackpad packets is always 02h.
  2. Sensor ID Byte [1] The sensor ID byte contains an unsigned 8-bit integer that specifies the ID of the sensor whose data is being transmitted. Sensor ID on the target side is typically established by the order of sensor pointers in the global sensor pointer array. Sensors are sorted alphabetically by the Captivate Design Center PC GUI.
  3. Max Touches [2] The max touches byte contains an unsigned 8-bit integer that specifies the number of simultaneous touches supported by the trackpad device. This also provides insight into the length of the remainder of the packet. Following the max touches byte, there will be 4 bytes for each simultaneous touch supported by the trackpad device. Trackpad devices must support at least one touch.
  4. Gesture Status [3] The trackpad gesture byte indicates whether a gesture was just detected, and what that gesture was. The value/gesture pairs are listed below. If no gesture was detected, the gesture byte will be set to FFh.
**Value**Gesture
00hWake on Proximity Detection
01hReserved
02hSingle Touch Single Tap
03hSingle Touch Double Tap
04hTwo Touch Single Tap
05hTwo Touch Double Tap
06hTap and Hold
07hTwo Touch Tap and Hold
08hSwipe Left
09hSwipe Right
0AhSwipe Up
0BhSwipe Down
0Ch-FEhReserved
FFhNo Gesture Detected
  1. Touch Coordinates [4-n], n=4+4(Max Touches) The touch coordinates indicate the presence of and location of a touch on the trackpad. Touch locations are communicated via two 16-bit unsigned integers per touch: one for the coordinate on the X axis, and one for the coordinate on the Y axis. 16 bits is the maximum resolution in either the X or Y direction for the trackpad. Most applications will have a working resolution that is less than 16 bits per coordinate, but 16 bits are always sent regardless. If no touch is present, both the X and the Y coordinates will read as FFFFh.

NOTE: Trackpad packets only pertain to dedicated trackpad devices.

Format: General Purpose Packets

General purpose packets are unique in that they simply serve as a data streaming mechanism for any data an application wishes to send. Data is sent as an array of 16-bit unsigned integers. General purpose packets have variable length that is dependent upon the number of integers being sent. There are always 2 control bytes. Following the control bytes is the data payload (up to 29 entries), with 2 bytes (16 bits) per entry.

lib_comm_protocol_gppacket.png
General Purpose Packet Format
  1. Command Byte [0] The command byte for general purpose packets is always 10h.
  2. Valid Data Count Byte [1] The valid data count byte indicates how many valid data entries exist in the packet. This also implies the length of the packet: each valid entry implies two bytes of payload (each entry is a 16-bit unsigned integer). Valid values for this field are 1-29.
  3. Data Payload [2-n] The data payload section contains the generic data to be transmitted, formatted as unsigned 16-bit integers. Up to 29 payload items (58 bytes) are allowed.

General purpose packets are generated via a call into the protocol layer. The CAPT_getGeneralPurposePacket() function generates a packet for the data array of length ui8Cnt pointed to by pData, and stores the generated packet in the buffer space pointed to by pBuffer.

DescriptionDeclaration
Get a General Purpose Packetextern uint16_t CAPT_getGeneralPurposePacket(uint16_t *pData, uint8_t ui8Cnt, uint8_t *pBuffer)

Format: Parameter Packets

Parameter packets have a fixed length of 7 bytes. There are 3 control bytes and 4 data payload bytes. The packet format can be seen below.

lib_comm_protocol_parameterpacket.png
Parameter Packet Format
  1. Command Byte [0] The command byte for parameter packets is variable, and indicates the ID of the parameter that is to be read or written to.
  2. Read/Write Byte [1] The read/write byte is a 1-bit Boolean value that indicates whether the operation is a parameter read or a parameter write. 1 = write, 0 = read.
  3. Sensor ID Byte [2] The sensor ID byte contains an unsigned 8-bit integer that specifies the ID of the sensor whose parameter is being read from or written to. Sensor ID on the target side is typically established by the order of sensor pointers in the global sensor pointer array. Sensors are sorted alphabetically by the Captivate Design Center PC tool. Note that parameters for controller commands (C-h commands) may not use the Sensor ID field.
  4. Data Bytes [3-6] The data bytes contain the information that is to be read or written to. In some cases, 1 or more of the data bytes are used for further ID (such as cycle ID or element ID).

The protocol layer provides several functions for framing and interpreting parameter packets. Placing a call into CAPT_processReceivedData() causes the protocol layer to search for potential packets in a datastream that has been queued up by a serial driver. Once a valid packed has been framed, the parameter may be accessed and updated/read via calls to CAPT_accessSensorParameter() and CAPT_accessSpecialSensorParameter().

DescriptionDeclaration
Access a Sensor Parameterextern tTLParameterAccessResult CAPT_accessSensorParameter(tSensor **sensorArray, tParameterPacket *pPacket)
Access a Special Sensor Parameterextern tTLParameterAccessResult CAPT_accessSpecialSensorParameter(tSensor **sensorArray, tParameterPacket *pPacket)
Identify and Frame a Packet in a Receive Data Queueextern bool CAPT_processReceivedData(tByteQueue *pReceiveQueue, tParameterPacket *pPacket, tTLProtocolProcessingVariables *pVariables)
Verify a Checksumextern bool CAPT_verifyChecksum(const uint8_t *pBuffer, const uint16_t ui16Length, const uint16_t ui16Checksum)

The available parameters that can be adjusted through the use of parameter packets are listed below.

Sensor Parameters

Parameter NameByte 0: CMDByte 1: RWByte 2: IDByte 3Byte 4Byte 5Byte 6MCU TaskSW Containing StructureSW Containing Variable
Conversion Gain80RWSensor IDDon't CareDon't CareATI Base Lower ByteATI Base Upper ByteRe-CaltSensorui16ConversionGain
Conversion Count81RWSensor IDDon't CareDon't CareATI Target Lower ByteATI Target Upper ByteRe-CaltSensorui16ConversionCount
Prox Threshold82RWSensor IDDon't CareDon't CareProx Threshold Lower ByteProx Threshold Upper ByteNAtSensorui16ProxThreshold
Prox Debounce-In Threshold84RWSensor IDDon't CareDon't CareProx Db InDon't CareNAtSensorProxDbThreshold .DbUp
Prox Debounce-Out Threshold85RWSensor IDDon't CareDon't CareProx Db OutDon't CareNAtSensorProxDbThreshold .DbDown
Touch Debounce-In Threshold86RWSensor IDDon't CareDon't CareTouch Db InDon't CareNAtSensorTouchDbThreshold .DbUp
Touch Debounce-Out Threshold87RWSensor IDDon't CareDon't CareTouch Db OutDon't CareNAtSensorTouchDbThreshold .DbDown
Sensor Timeout Threshold88RWSensor IDDon't CareDon't CareSensor Timeout Lower ByteSensor Timeout Upper ByteNAtSensorui16TimeoutThreshold
Count Filter Enable89RWSensor IDDon't CareDon't CareCount Filter Enable bitDon't CareNAtSensorbCountFilterSelect
Count Filter Beta8ARWSensor IDDon't CareDon't CareCount Filter BetaDon't CareNAtSensorui8CntBeta
LTA Filter Beta8BRWSensor IDDon't CareDon't CareLTA Filter BetaDon't CareNAtSensorui8LTABeta
Halt LTA Filter Immediately8CRWSensor IDDon't CareDon't CareLTA Filter HaltDon't CareNAtSensorbSensorHalt
Runtime Re-Calibration Enable8DRWSensor IDDon't CareDon't CareRuntime Re-Cal EnableDon't CareNAtSensorbReCalibrateEnable
Force Re-Calibrate8EN/ASensor IDDon't CareDon't CareDon't CareDon't CareRe-CaltSensorN/A
Bias Current8FRWSensor IDDon't CareDon't CareBias CurrentDon't CareRe-CaltSensorui8BiasControl
Sample Capacitor Discharge95RWSensor IDDon't CareDon't CareCs DischargeDon't CareRe-CaltSensorbCsDischarge
Modulation Enable96RWSensor IDDon't CareDon't CareMod EnableDon't CareRe-CaltSensorbModEnable
Frequency Divider97RWSensor IDDon't CareDon't CareFreq DivDon't CareRe-CaltSensorui8FreqDiv
Charge/Hold Phase Length98RWSensor IDDon't CareDon't CareCharge LengthDon't CareRe-CaltSensorui8ChargeLength
Transfer/Sample Phase Length99RWSensor IDDon't CareDon't CareTransfer LengthDon't CareRe-CaltSensorui8TransferLength
Error Threshold9ARWSensor IDDon't CareDon't CareError Threshold Lower ByteError Threshold Upper ByteNAtSensorui16ErrorThreshold
Negative Touch Threshold9BRWSensor IDDon't CareDon't CareNegative Touch Threshold Lower ByteNegative Touch Threshold Upper ByteNAtSensorui16NegativeTouchThreshold
Idle State9CRWSensor IDDon't CareDon't CareIdle StateDon't CareNAtSensorbIdleState
Input Sync9DRWSensor IDDon't CareDon't CareInput SyncDon't CareNAtSensorui8InputSyncControl
Timer Sync9ERWSensor IDDon't CareDon't CareTimer SyncDon't CareNAtSensorbTimerSyncControl
Automatic Power-Down Enable9FRWSensor IDDon't CareDon't CarePower Down ControlDon't CareNAtSensorbLpmControl
Halt LTA on Sensor Prox or TouchA0RWSensor IDDon't CareDon't CareSensor Prox/Touch HaltDon't CareNAtSensorbPTSensorHalt
Halt LTA on Element Prox or TouchA1RWSensor IDDon't CareDon't CareElement Prox/Touch HaltDon't CareNAtSensorbPTElementHalt

Element Parameters

Parameter NameByte 0: CMDByte 1: RWByte 2: IDByte 3Byte 4Byte 5Byte 6MCU TaskSW Containing StructureSW Containing Variable
Touch Threshold83RWSensor IDCycle # (relative to sensor)Lower 4 Bits: Element # (relative to cycle)Touch ThresholdDon't CareNAtElementui8TouchThreshold [Element #]
Coarse Gain RatioA2RSensor IDCycle # (relative to sensor)[UPPER 4 Bits: Frequency #] [ LOWER 4 Bits: Element # relative to cycle] Coarse GainDon't CareNAtCaptivateElementTuningui8GainRatioCoarse
Fine Gain RatioA3RSensor IDCycle # (relative to sensor)[UPPER 4 Bits: Frequency #] [ LOWER 4 Bits: Element # relative to cycle] Fine GainDon't CareNAtCaptivateElementTuningui8GainRatioFine
Parasitic Offset ScaleD0RSensor IDCycle # (relative to sensor)[UPPER 4 Bits: Frequency #] [ LOWER 4 Bits: Element # relative to cycle] Offset Scale (2 bit selection)Don't CareNAtCaptivateElementTuningui16OffsetTap Upper Byte
Parasitic Offset LevelD1RSensor IDCycle # (relative to sensor)[UPPER 4 Bits: Frequency #] [ LOWER 4 Bits: Element # relative to cycle] Offset Level (8 bit selection)Don't CareNAtCaptivateElementTuningui16OffsetTap Lower Byte

Slider/Wheel Parameters

Parameter NameByte 0: CMDByte 1: RWByte 2: IDByte 3Byte 4Byte 5Byte 6MCU TaskSW Containing StructureSW Containing Variable
Slider/Wheel Position Filter Enable90RWSensor IDDon't CareDon't CareSlider Filter Enable bitDon't CareNAtSliderSensorParamsSliderFilterEnable
Slider/Wheel Position Filter Beta91RWSensor IDDon't CareDon't CareSlider Filter BetaDon't CareNAtSliderSensorParamsSliderBeta
Desired Slider/Wheel Resolution92RWSensor IDDon't CareDon't CareSlider Resolution Lower ByteSlider Resolution Upper ByteNAtSliderSensorParamsui16Resolution
Slider Lower Trim93RWSensor IDDon't CareDon't CareSlider Lower Trim Lower ByteSlider Lower Trim Upper ByteNAtSliderSensorParamsSliderLower
Slider Upper Trim94RWSensor IDDon't CareDon't CareSlider Upper Trim Lower ByteSlider Upper Trim Upper ByteNAtSliderSensorParamsSliderUpper

Controller/Management Parameters

Parameter NameByte 0: CMDByte 1: RWByte 2: IDByte 3Byte 4Byte 5Byte 6MCU TaskSW Containing StructureSW Containing Variable
Element Data Transmit EnableC0RWDon't CareDon't CareDon't CareElement Transmit EnableDon't CareNAtCaptivateApplicationbElementDataTxEnable
Sensor Data Transmit EnableC1RWDon't CareDon't CareDon't CareSensor Transmit EnableDon't CareNAtCaptivateApplicationbSensorDataTxEnable
Active Mode Scan Rate (ms)C2RWDon't CareDon't CareDon't CareActive Report Period (ms) Lower ByteActive Report Period (ms) Upper Byte NAtCaptivateApplicationui16ActiveModeScanPeriod
Wake-on-Prox Mode Scan Rate (ms)C3RWDon't CareDon't CareDon't CareWoP Report Period (ms) Lower ByteWoP Report Period (ms) Upper Byte NAtCaptivateApplicationui16WakeOnProxModeScanPeriod
Wakeup IntervalC4RWDon't CareDon't CareDon't CareWakeup IntervalDon't CareNAtCaptivateApplicationui8WakeupInterval
Inactivity TimeoutC5RWDon't CareDon't CareDon't CareTimeout Lower ByteTimeout Upper ByteNAtCaptivateApplicationui16InactivityTimeout

UART Driver

The MSP430 eUSCI_A UART Driver provides a simple UART API to MSP430 applications, enabling interrupt-driven transmit and receive operations as well as error handling. This section provides an overview of the driver's features, architecture, and API. This document assumes that the reader is familiar with the MSP430 MCU architecture, as well as embedded C programming concepts.

Related Documents

This document should be used with the following additional supporting documentation. The MSP430 Driver Library API is not specific to this driver, and has its own documentation.

  • MSP430 Driver Library (DriverLib) User's Guide
  • The relevant MSP430 Family User's Guide

Purpose of the Driver

The eUSCI_A UART Driver enables developers to quickly get up and running with UART communication via an API that is similar to one used on a PC. The API provides simple functions such as UART_openPort() and UART_transmitBuffer(), and it allows the developer to register event handlers for received data and error conditions. To enable portability, the driver is built upon the MSP430 DriverLib register abstraction. This driver has been designed to work with MSP430 DriverLib build 1.90.00.00 and greater. Like any other serial interface, UART has benefits and drawbacks. Whether or not it is the best choice for an embedded interface depends on a number of factors. The benefits and drawbacks of UART are discussed below.

UART Benefits

  • Ease of implementation. UART is one of the most common serial communication protocols around. While interfaces with higher speed and higher reliability exist, such as I2C and SPI, none match UART for ease of implementation. UART is easily interfaced to a host PC or another microcontroller, and as such it is well suited for use as a debug console.
  • Point-to-point simplicity. Since only two nodes exist, the interface is not a bus with multiple devices attached. This removes the need for addressing or chip-select overhead.
  • Full duplex communication. In this UART driver implementation, transmit and receive operations are decoupled and may occur concurrently. Each of the two connected nodes may consider itself a master, and may begin transmission to the other node at any time.

UART Drawbacks

  • Critical bit timing. Because UART transmission is asynchronous (there is no bit clock), accurate timing must be guaranteed on both sides of the interface.
  • Bit rate limitation. Due to the critical bit timing above, UART is typically more limited in maximum bit clock then when compared with a synchronous interface, such as SPI or I2C.
  • Critical ISR timing. This driver does not employ the use of a DMA channel for moving data from transmit and receive buffers into RAM, as not all devices are equipped with a DMA engine. Instead of DMA, all data transfers are handled via interrupt service routines. This means that the CPU must be available to service receive interrupts at least every byte period. A byte period is defined as the transmission time for one byte, which is equal to 10 times the bit period. Note that the byte transmission time increases if parity or more than one stop bit is used. For example, if the bit clock is 250 kHz, the transmission time of one byte is 4us times 10 bits, or 40us. With an 8MHz CPU clock, there are only 320 instruction cycles available every 40us. If the CPU is not available to service receive interrupts, received bytes may be lost. The driver provides an error detection mechanism to alert the application if and when data is being lost.
  • Asynchronous overhead. UART as configured in this driver requires at least one start bit and one stop bit. As such, each 8-bit byte requires that 10 bits be sent. This overhead would not be present in a SPI interface, although I2C does impose an ACK/NACK bit as well as addressing overhead.

Driver Features

The key features implemented in the UART driver are listed below.

  • Full duplex bi-directional communication
  • No flow control required
  • Fully interrupt-driven
  • Non-blocking transmit function
  • Receive event callback
  • Error event callback
  • Compile-time selection of which eUSCI_A instance to use

Driver Overview

The UART driver is provided in source code. The driver consists of three source files:

  • UART_Definitions.h (Configuration header file)
  • UART.h (API Header file)
  • UART.c (API Implementation file) The core driver is implemented in UART.h and UART.c. The UART_Definitions.h file allows the developer to adjust the driver's compile-time options.

The UART driver requires the following hardware resources:

  • One eUSCI_A peripheral instantiation
  • Two device pins (UCAxTXD and UCAxRXD), where 'x' represents the selected eUSCI_A instance

The UART driver API is composed of functions and data types. The sections below describe how to configure the UART driver and use it to perform transmit and receive operations. The UART driver employs a basic software state machine to manage operations. The three driver states and their interconnection are shown in the diagram below.

lib_comm_uartdriverstatemachine.png
UART Driver State Machine

Compile-time Driver Configuration Options

The UART driver has compile-time options as well as run-time configurable options. Certain things must be known at compile-time, such as which eUSCI_A peripheral instance is associated with the driver. Other options, such as baud rate, may be controlled at runtime.

The compile-time configuration options are set in the UART_Definitions.h file. The available compile-time options are described below.

UART Enable Definition

ParameterFileValid Values
UART__ENABLEUART_Definitions.htrue, false

The UART Enable compile-time option selects whether the UART Driver is enabled or disabled. This provides a mechanism to exclude the driver from the compilation process. To include the driver, define UART__ENABLE as true. Else, define it as false.

UART eUSCI_A Peripheral Selection Definition

ParameterFileValid Values
UART__EUSCI_A_PERIPHERALUART_Definitions.hEUSCI_A0_BASE, EUSCI_A1_BASE

The UART eUSCI_A peripheral selection allows easy selection of which eUSCI_A instance to associate with the UART driver. This provides flexibility during design if a pin-mux change is necessary. A valid base address must be provided.When a eUSCI_A peripheral selection is made, the UART driver ISR address is linked to the appropriate eUSCI interrupt vector automatically. If an invalid address is selected, a compiler error is thrown.

UART Low Power Mode (LPMx)

ParameterFileValid Values
UART__LPMx_bitsUART_Definitions.h0, LPM0_bits, LPM1_bits, LPM2_bits, LPM3_bits

UART communications is often performed in a low power mode. The UART LPM Mode configuration is used in three ways, as described below.

  • The receive callback and error callback functions may trigger an active exit from a low power mode to wake the CPU for further action. If a callback function returns a Boolean true, the UART driver will wake the CPU after returning from the callback. It does this by clearing the status register bits indicated in the UART LPM Mode configuration upon exit from the UART ISR.
  • A transmit operation will exit the low power mode specified by the UART LPM Mode configuration once the full transmission is complete, to allow the application to know that the transmission was completed.
  • The transmit function is normally a non-blocking function. Once transmission begins, the function returns. However, in the event that a previous transmission was still in progress when the transmit function was called, there is an option to wait for the previous transmission to complete inside of a low power mode. If that option is selected, the low power mode specified by the UART LPM Mode configuration will be entered while waiting. The transmit function will know to begin transmission when the previous transmission exits active (per number 2 above).

Run-time Driver Configuration Options

The UART driver has compile-time options as well as run-time configurable options. The runtime configuration options are specified by populating a tUARTPort structure in the application, and passing this structure to the driver when opening the driver. A tUARTPort structure is required when opening the UART driver. The tUARTPort structure must be available in memory whenever the driver is open. This is a result of the fact that the UART driver references this structure at runtime to find the callback functions for receive handling and error handling. However, the driver does not modify the data in the structure at any time, and as such, the structure may be placed in a read-only memory section (such as C const memory). These parameters are considered runtime adjustable because the parameters may be modified by the application if the UART driver is closed first, then re-opened. For example, the application may change the UART baud rate by closing the port, changing the baud rate options on the tUARTPort structure, and re-opening the port. Note that the .peripheralParameters member of the tUARTPort structure is a EUSCI_A_UART_initParam structure from the MSP430 Driver Library.

MemberDescriptionValid Values
bool (*pbReceiveCallback)(uint8_t)pbReceiveCallback is a function pointer that may point to a receive event handler. If no receive handling is required, initialize this member to 0 (null).Null, or a valid function address.
bool (*pbErrorCallback)(uint8_t)pbErrorCallback is a function pointer that may point to an error event handler. If no error handling is required, initialize this member to 0 (null).Null, or a valid function address.
.peripheralParameters.selectClockSourceThis member specifies the clock source for the eUSCI_A peripheral.EUSCI_A_UART_CLOCKSOURCE_SMCLK, EUSCI_A_UART_CLOCKSOURCE_ACLK
.peripheralParameters.clockPrescalarThis member specifies the eUSCI_A clock prescalar. This affects the baud rate.0-65535
.peripheralParameters.firstModRegThis member specifies the eUSCI_A first stage modulation. This affects the baud rate.0-15
.peripheralParameters.secondModRegThis member specifies the eUSCI_A second stage modulation. This affects the baud rate.0-255
.peripheralParameters.parityThis member specifies the UART parity mode.EUSCI_A_UART_NO_PARITY, EUSCI_A_UART_ODD_PARITY, EUSCI_A_UART_EVEN_PARITY
.peripheralParameters.msborLsbFirstThis member specifies the transmission bit order.EUSCI_A_UART_LSB_FIRST, EUSCI_A_UART_MSB_FIRST
.peripheralParameters.numberofStopBitsThis member specifies the number of stop bits.EUSCI_A_UART_ONE_STOP_BIT, EUSCI_A_UART_TWO_STOP_BITS
.peripheralParameters.uartModeThis member specifies the UART mode.EUSCI_A_UART_MODE, EUSCI_A_UART_IDLE_LINE_MULTI_PROCESSOR_MODE, EUSCI_A_UART_ADDRESS_BIT_MULTI_PROCESSOR_MODE, EUSCI_A_UART_AUTOMATIC_BAUDRATE_DETECTION_MODE
.peripheralParameters.overSamplingThis member specifies whether UART oversampling is enabled.EUSCI_A_UART_OVERSAMPLING_BAUDRATE_GENERATION, EUSCI_A_UART_LOW_FREQUENCY_BAUDRATE_GENERATION

Using the Driver: Opening and Closing the Driver

Opening and closing of the UART driver is accomplished through the following function calls:

DescriptionDeclaration
Open the UART Portextern void UART_openPort(const tUARTPort *pPort)
Close the UART Portextern void UART_closePort(void)

The UART driver is opened and initialized by a call to UART_openPort(), which is passed a completed tUARTPort structure. The tUARTPort structure must be populated by the application. It may be placed in read-only memory, as the UART API does not modify the structure at any time; rather, it only references it. It is important that the structure be left in memory at the address given when UART_openPort() is called, as the UART API will reference this structure to access callback functions when the port is open. If the memory must be freed, first close the UART port with a call to UART_closePort().

A call to UART_closePort() will disable the UART port and its associated eUSCI_A peripheral, halting all Rx/Tx interrupt activity. After the port is closed, the event handlers will no longer be called, and the tUARTPort structure memory may be released to the application.

Opening a Port

//
// g_myUartPort specifies the UART port configuration that is passed
// to the UART port driver during init.
// The UART configuration is 9600B8N1, sourced by a 4MHz SMCLK.
//
const tUARTPort g_myUartPort =
{
.pbReceiveCallback = &receiveHandler,
.pbErrorCallback = &errorHandler,
.peripheralParameters.selectClockSource = EUSCI_A_UART_CLOCKSOURCE_SMCLK,
.peripheralParameters.clockPrescalar = 26,
.peripheralParameters.firstModReg = 0,
.peripheralParameters.secondModReg = 0xB6,
.peripheralParameters.parity = EUSCI_A_UART_NO_PARITY,
.peripheralParameters.msborLsbFirst = EUSCI_A_UART_LSB_FIRST,
.peripheralParameters.numberofStopBits = EUSCI_A_UART_ONE_STOP_BIT,
.peripheralParameters.uartMode = EUSCI_A_UART_MODE,
.peripheralParameters.overSampling = EUSCI_A_UART_OVERSAMPLING_BAUDRATE_GENERATION
};
UART_openPort(&g_myUartPort);

Using the Driver: Transmitting Data

Data transmission is accomplished through the following function calls:

DescriptionDeclaration
Transmit a Bufferextern void UART_transmitBuffer(const uint8_t *pBuffer, uint16_t ui16Length)
Transmit a Byteextern void UART_transmitByteImmediately(uint8_t ui8Data)
Get Port Statusextern uint8_t UART_getPortStatus(void)

Transmit operations are interrupt driven. To initiate a transmission after opening the UART port, make a call to UART_transmitBuffer(). This function is simply passed a pointer to the buffer to transmit, and the length of the buffer to transmit (specified in bytes). If the eUSCI_A peripheral is available, transmission will begin immediately and the function will return. If the peripheral is still busy sending a previous transmission, it will block (in a low power mode, if specified in UART_Definitions.h) until the previous transmission is complete.

Transmitting a Buffer

UART_transmitBuffer("\n\rHello World!\n\r", sizeof("\n\rHello World!\n\r"));
lib_comm_uartdrivertxflow.png
UART Transmit Flow Diagram

The transmit operation uses the buffer memory that was pointed to in the UART_transmitBuffer() function - it does not perform a data copy in the name of efficiency. As such, that memory must not be modified during the transmission, or the transmission will be invalid. To detect when transmission has completed and the memory is again available, it is possible to check the UART port status via a call to UART_getPortStatus(). If an application will be re-using buffer space, it is best to employ a ping-pong buffer strategy so that a new packet may be assembled while a previous packet is being sent.

A call to UART_getPortStatus() returns one of the values enumerated by tUARTStates. The options are described below.

Port Status OptionDescription
eUARTIsClosedThe UART driver is not currently open, and is neither receiving nor transmitting data.
eUARTIsIdleThe UART driver is currently open, but is not in the process of transmitting a buffer.
eUARTIsTransmittingThe UART driver is currently open, and is in the process of transmitting a buffer.

If only a single byte is going to be sent, it is possible to send it immediately via the UART_transmitByteImmediately() function. This function sends a single byte as soon as the eUSCI_A peripheral transmit buffer is available. It will block until the buffer is available. If a transmission started by a call to UART_transmitBuffer() is in progress, this transfer may finish first as it is interrupt-driven. The UART_transmitByteImmediately() function is mainly available to be used in terminal emulator applications, where sent ASCII characters are echoed back to be visible to the user.

Transmitting a Single Byte

uint8_t ui8Data = 0x01;
UART_transmitByteImmediately(ui8Data);

Using the Driver: Receiving Data

Received data is passed to the application through the use of the receive callback. The UART driver will call a user-defined function every time a new byte is received from the UART peripheral, passing that byte to the receive callback. It is then up to the application to decide how to handle the data. It is recommended to use a ring-buffer FIFO queue to handle buffering incoming data. The data may then be extracted and processed in a background process. The receive event handler is called from the UART ISR in the UART driver, and as such, the event handler should be kept as short as possible. It is recommended to follow the same practice used to write ISR's when writing event handlers.

The receive callback function is linked to the driver by placing its address in the pbReceiveCallback member of the tUARTPort structure. If the application does not wish to listen for receive events, the receive callback pointer in the tUARTPort structure may be initialized to null. Receive operations are performed entirely out of the ISR. The receive operation ISR flow is shown below. Errors are checked for whenever a receive interrupt is serviced.

lib_comm_uartdriverrxflow.png
UART Receive Flow Diagram

Using the Driver: Error Handling

The UART driver employs basic UART error detection to alert the application when something has gone wrong. The two errors detected by the driver are:

  1. UART Receive Buffer Overrun. This occurs if a new byte is placed into the eUSCI_A UART receive buffer before a previous byte was read out. This occurs when there is not enough CPU bandwidth to read out data at the rate data is coming into the peripheral. To resolve this error during development, it is necessary to either: slow down the rate at which data is received, whether through delays between bytes or a lower baud rate, or to increase the CPU availability by shortening interrupt handlers or increasing the CPU clock frequency. The enumerated error flag is eUARTOverrunError.
  2. UART Framing Error. This typically occurs if there is a baud rate mismatch between devices. The enumerated error flag is eUARTFramingError. The driver alerts the application via the error callback function. This works similar to the receive callback, except the error code is passed rather than the received byte. The error callback function is called from the UART ISR, and as such, it should be kept as short as possible. The error callback function is linked to the driver by placing its address in the pbErrorCallback member of the tUARTPort structure. If the application does not wish to listen for error events, the error callback pointer in the tUARTPort structure may be initialized to null.

Using the Driver: Example Application

This example listens for a ASCII line on eUSCI_A0. When a line is received, it is echoed back. The baud rate is 9600-8N1. The max line size is 64 bytes.

#include "driverlib.h"
#include "UART.h"
//
// DCO_FREQ defines the MSP430 DCO frequency in Hz
//
#define DCO_FREQ 8000000
//
// REFO_FREQ defines the REFO frequency in Hz
//
#define REFO_FREQ 32768
//
// RECEIVE_BUFFER_SIZE defines the size of the UART receive buffer.
//
#define RECEIVE_BUFFER_SIZE 64
//
// g_ui8ReceiveBuffer is the UART receive buffer. It is filled with
// incoming characters via the UART receive event handler.
//
uint8_t g_ui8ReceiveBuffer[RECEIVE_BUFFER_SIZE];
//
// g_ui8ReceiveIndex is the UART receive buffer index. It defines the
// buffer index that the next received byte is placed into.
//
uint8_t g_ui8ReceiveIndex;
//
// g_bGotNewLine is a flag used by the UART receive event handler to
// signal the background loop (the main application thread) that a full
// ASCII line has been received in the buffer.
//
bool g_bGotNewLine = false;
//
// The UART receive handler is called by the UART port driver whenever a new
// character is received.
//
bool receiveHandler(uint8_t ui8Data)
{
if (g_bGotNewLine == false)
{
if (g_ui8ReceiveIndex<RECEIVE_BUFFER_SIZE)
{
//
// It is safe to receive the new character into the buffer.
// If the data is a ASCII return, signal the background app
// and exit awake.
//
g_ui8ReceiveBuffer[g_ui8ReceiveIndex++] = ui8Data;
if (ui8Data=='\r')
{
g_bGotNewLine = true;
return true;
}
else
{
UART_transmitByteImmediately(ui8Data);
}
}
else
{
//
// The buffer is full, return new line to print what
// we have right now
//
g_bGotNewLine = true;
return true;
}
}
return false;
}
//
// The UART error handler is called by the UART port driver whenever an
// error on the UART port is detected.
// The application could handle errors here if desired (such as a UART buffer
// overrun or framing error).
//
//
bool errorHandler(uint8_t ui8Error)
{
return false;
}
//
// g_myUartPort specifies the UART port configuration that is passed
// to the UART port driver during init.
// The UART configuration is 9600B8N1, sourced by SMCLK.
//
const tUARTPort g_myUartPort =
{
.pbReceiveCallback = &receiveHandler,
.pbErrorCallback = &errorHandler,
.peripheralParameters.selectClockSource = EUSCI_A_UART_CLOCKSOURCE_SMCLK,
.peripheralParameters.clockPrescalar = 26,
.peripheralParameters.firstModReg = 0,
.peripheralParameters.secondModReg = 0xB6,
.peripheralParameters.parity = EUSCI_A_UART_NO_PARITY,
.peripheralParameters.msborLsbFirst = EUSCI_A_UART_LSB_FIRST,
.peripheralParameters.numberofStopBits = EUSCI_A_UART_ONE_STOP_BIT,
.peripheralParameters.uartMode = EUSCI_A_UART_MODE,
.peripheralParameters.overSampling = EUSCI_A_UART_OVERSAMPLING_BAUDRATE_GENERATION
};
//
// Application main()
//
void main(void)
{
//
// Set up the MCU with a 8MHz MCLK, 4MHz SMCLK, and ~32.768kHz ACLK.
//
WDT_A_hold(WDT_A_BASE);
CS_clockSignalInit(CS_FLLREF, CS_REFOCLK_SELECT, CS_CLOCK_DIVIDER_1);
CS_initFLLSettle(DCO_FREQ/1000, DCO_FREQ/REFO_FREQ);
CS_clockSignalInit(CS_MCLK, CS_DCOCLKDIV_SELECT, CS_CLOCK_DIVIDER_1);
CS_clockSignalInit(CS_SMCLK, CS_DCOCLKDIV_SELECT, CS_CLOCK_DIVIDER_2);
CS_clockSignalInit(CS_ACLK, CS_REFOCLK_SELECT, CS_CLOCK_DIVIDER_1);
__delay_cycles(8000000);
//
// Set P1.0 and P1.1 to UCA0TXD and UCA0RXD, respectively.
//
PM5CTL0 &= ~LOCKLPM5;
GPIO_setAsPeripheralModuleFunctionOutputPin(GPIO_PORT_P1, GPIO_PIN4, GPIO_PRIMARY_MODULE_FUNCTION);
GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_P1, GPIO_PIN5, GPIO_PRIMARY_MODULE_FUNCTION);
//
// Enable masked interrupts.
//
__bis_SR_register(GIE);
//
// Open the UART port and send the welcome message!
//
UART_openPort(&g_myUartPort);
UART_transmitBuffer("\n\rHello World!\n\r", sizeof("\n\rHello World!\n\r"));
UART_transmitBuffer("Waiting for a new line... ", sizeof("Waiting for a new line... "));
//
// Application background loop
//
while(1)
{
//
// If a new line was received, echo it back!
//
if (g_bGotNewLine == true)
{
UART_transmitBuffer("\n\rReceived: ", sizeof("\n\rReceived: "));
UART_transmitBuffer(g_ui8ReceiveBuffer, g_ui8ReceiveIndex);
UART_transmitBuffer("\n\r", sizeof("\n\r"));
UART_transmitBuffer("Waiting for a new line: ", sizeof("Waiting for a new line: "));
g_ui8ReceiveIndex = 0;
g_bGotNewLine = false;
}
//
// Sleep in LPM0 (CPU off, DCO on).
// The UART receive event handler will
// wake up the background loop if a line is received.
//
LPM0;
}
}

I2C Slave Driver

The MSP430 eUSCI_B I2C slave driver provides a simple I2C slave API to MSP430 applications, enabling interrupt-driven I2C read/write operations as well as bus timeout detection and driver error handling. This User's Guide provides an overview of the driver's features, architecture, and API. This document assumes that the reader is familiar with MSP430 MCU architecture, as well as embedded C programming concepts and basic I2C principles. Note: From this point forward, the I2C slave driver will simply be referred to as "the driver."

Related Documents

This guide should be used with the following additional supporting documentation. The MSP430 Driver Library API is not specific to this driver, and has its own documentation.

  • MSP430 Driver Library (DriverLib) User's Guide
  • The relevant MSP430 Family User's Guide

Purpose of the Driver

The driver enables quick development of MSP430 applications where the MSP430 itself is a slave device to some other master in a larger embedded system. This is a common application, as MSP430 microcontrollers are often a secondary MCU to a larger host MCU or host MPU.

The driver is structured to be flexible, enabling many different applications. It is capable of providing a register-file interface to a host processor, similar to a sensor or an EEPROM. It may also be used as a bulk-transfer interface to a host.

Driver Features

The key features provided by the driver are listed below.

  • Interrupt-driven I2C slave software state machine
  • I2C Write (master transmitter, slave receiver) event callback
  • I2C Read (master receiver, slave transmitter) buffer assignment
  • Support for I2C start/stop/start as well as I2C start/repeated-start sequences
  • I2C error/warning reporting via an error callback function
  • Operation in LPM3 between I2C interrupts when timeout feature is used
  • Operation in LPM4 between I2C interrupts when timeout feature is not used
  • Open drain slave request signal (to alert the master to service the slave)
  • Slave request timeout capability to prevent an application stall
  • I2C bus transaction timeout capability to prevent an application stall

Driver Overview

The driver is provided in C source code. It consists of 3 base driver source files, plus an additional 3 source files related to the I2C timeout detection feature (which is a compile-time option).

Base Driver:

  • I2CSlave_Definitions.h (Configuration header file)
  • I2CSlave.h (API header file)
  • I2CSlave.c (API implementation file)

Function Timer (used for I2C timeout detection):

  • FunctionTimer_Definitions.h (Configuration header file)
  • FunctionTimer.h (API header file)
  • FunctionTimer.c (API source file)

The I2CSlave_Definitions.h and FunctionTimer_Definitions.h files contain the compile-time options for each component.

The driver requires the following MCU hardware resources:

Base Driver:

  • One eUSCI_B peripheral instantiation
  • Two device pins (UCBxSDA and UCBxSCL), where 'x' represents the selected eUSCI_B instance

Slave Request Feature (Optional):

  • One additional device pin (Any digital IO for the slave request line feature).

Timeout Feature (Optional):

  • One TIMER_A peripheral instantiation with at least 2 capture-compare units (CCR0 and CCR1)

The driver operation is based upon a software state machine that keeps track of the current driver state. There are four possible states: closed, idle, read, and write. Since the driver implements an I2C slave, it is important to be clear on the naming conventions for read and write. An I2C bus write is a receive operation from the perspective of an I2C slave, as the bus master is writing to the slave. Similarly, an I2C bus read is a transmit operation from the perspective of the slave. The state machine implemented by the driver is depicted below.

lib_comm_i2cslavestatemachine.png
I2C Slave Driver State Machine

As shown, state changes between idle, read, and write are controlled solely by the I2C bus master. It is possible for the slave to close the driver at any time, however.

Driver Overview: I2C Receive Operations (I2C Bus Write)

The driver enters the "write" state (eI2CSlaveIsBeingWrittenTo) whenever the bus master sends a start or restart condition to the driver's 7-bit I2C address with the R/_W bit cleared. In this state, the driver is receiving data. Data is received into the buffer memory that was specified by the user when the driver was opened. If the bus master attempts to write data beyond the length of the receive buffer, the data is ignored and an error callback alerts the application. The write state is cleared when a stop condition or a restart condition is issued. When this happens, the driver calls a user-specified callback function for the received data to be handled.

Driver Overview: I2C Transmit Operations (I2C Bus Read)

The driver enters the "read" state (eI2CSlaveIsBeingRead) whenever the bus master sends a start or restart condition to the driver's 7-bit I2C address with the R/_W bit set. In this state, the driver is transmitting data to the master by loading data from the latest transmit buffer memory that was linked to the driver. Note that the buffer memory must have been pre-loaded before the read state was entered. If the bus master attempts to read data before any buffer was specified or if it attempts to read out more data than was made available in the buffer, the driver will clock out an invalid byte, which may be specified as a compile-time option. The read state is cleared when a stop condition or a restart condition is issued. When this happens, the driver wakes the CPU from any low power modes in case it was pending on the completion of a read operation.

Driver Overview: Slave Request Feature

The driver provides a mechanism for alerting the bus master that it wishes to communicate. This is helpful in many applications, as the slave has no way to initiate communication on the I2C bus. The slave request feature is implemented as an open-drain request line. The request line may be any digital IO on the MCU. The request line should be pulled up to VCC via a pull-up resistor, just like an I2C bus line. The driver controls whether the request line IO is left tri-stated (Hi-Z), or whether it is pulled to a logic low (sinking current to ground through the pullup resistor and the digital IO). The driver API contains a function for "pulling" the request line, which waits for a I2C bus response from the master (or a timeout) before returning.

Driver Overview: Timeout Feature (Function Timer)

The driver provides a timeout mechanism for preventing application lock-ups. To enable timeouts in a low-power way, a dedicated hardware timer is used to set limits on how long specific driver operations may take. The operations that may have timeouts placed on them are:

  1. I2C Slave Request Timeout. The slave request feature described in section 2.3.3 signals the bust master to communicate with the slave. That slave request API function is designed to block until the master starts communication (via a I2C bus read start condition). If the timeout feature is enabled, the slave request function will time out automatically if the master does not start a read within the specified timeout period.
  2. I2C Transaction Timeout. The transaction timeout places a limit on how long any I2C bus transaction may take. An I2C transaction time is defined between a start or restart condition until a restart or stop condition is issued. The goal of the transaction timeout is to detect scenarios where the bus master went down in the middle of transmission, or an I2C bus disconnection.

The timeout feature is implemented by the Function Timer module, which is a completely independent software module. As transaction monitoring may be a part of the application layer (perhaps with a standard watchdog timer), the timeout feature may be excluded completely at compile time. The Function Timer module essentially calls a predefined function in the foreground after a defined delay in timer cycles. That function can then take actions such as cancelling a slave request or resetting the driver.

Compile-time Driver Configuration Options

The compile-time configuration options are set in the I2CSlave_Definitions.h and FunctionTimer_Definitions.h files. These options are described below.

I2C Slave Enable Definition

ParameterFileValid Values
I2CSLAVE__ENABLEI2CSlave_Definitions.htrue, false

The I2C Slave Enable compile-time option selects whether the driver is enabled or disabled. This provides a mechanism to exclude the driver from the compilation process. To include the driver, define I2CSlave__ENABLE as true. Else, define it as false.

I2C eUSCI_B Peripheral Selection Definition

ParameterFileValid Values
I2CSLAVE__EUSCI_B_PERIPHERALI2CSlave_Definitions.hEUSCI_B0_BASE

The I2C eUSCI_B peripheral selection allows easy selection of which eUSCI_B instance to associate with the I2C driver. This provides flexibility during design if a pin-mux change is necessary. A valid base address must be provided.

I2C Slave Address Definition

ParameterFileValid Values
I2CSLAVE__ADDRESSI2CSlave_Definitions.h0x00 to 0x7F

The I2C slave address specifies the 7-bit I2C bus address associated with this device.

I2C Slave LPM Mode Definition

ParameterFileValid Values
I2CSLAVE__LPMx_bitsI2CSlave_Definitions.hLPM0_bits, LPM1_bits, LPM2_bits, LPM3_bits, LPM4_bits

I2C communication may be performed in a low power mode. No clocks need to be enabled on the MSP430 to send or receive bytes on a eUSCI_B peripheral when in I2C slave mode. This is a benefit of the bit clock being provided by the bus master. The I2C Slave LPM mode control is used in the following ways.

  1. Receive callback functions may trigger an active exit from the I2C ISR after the completion of a receive event (triggered by either a re-start or a stop condition). If a receive callback returns true to the driver, the status register bits specified by the I2C slave LPM mode will be cleared when exiting.
  2. A call to I2CSlave_setTransmitBuffer() will block if a previous read is requested or in progress. If a low power mode is specified via the I2C Slave LPM Mode definition, the block will take place in that low power mode- otherwise, it will block in active mode.

I2C Slave Invalid Byte Definition

ParameterFileValid Values
I2CSLAVE__INVALID_BYTEI2CSlave_Definitions.h0x00 to 0xFF

The invalid byte specifies the byte that is transmitted to the master (during an I2C read) if the master attempts to read beyond the length of the transmit buffer provided to the driver.

I2C Slave Timeout Enable Definition

ParameterFileValid Values
I2CSLAVE__TIMEOUT_ENABLEI2CSlave_Definitions.htrue, false

The slave timeout enable controls whether the timeout feature of the driver is included. The timeout feature provides the ability to set a maximum amount of time a certain I2C slave task may take before it fails. The two tasks that may be monitored with a timeout is the I2C slave request and any I2C transaction. Note that enabling the timeout feature requires the inclusion of the FunctionTimer source files, a Timer_A instance, and additional memory.

I2C Slave Request Timeout Cycles Definition

ParameterFileValid Values
I2CSLAVE__REQ_TIMEOUT_CYCLESI2CSlave_Definitions.h0x0000 - 0xFFFF

The I2C slave request timeout cycles specifies the timeout period for the I2C request timeout, in units of the function timer clock period. This is the amount of time the driver will wait after pulling the slave request line low before the transaction fails out. The bus master must respond to the slave request within this amount of time.

I2C Slave Transfer Timeout Cycles Definition

ParameterFileValid Values
I2CSLAVE__TXFR_TIMEOUT_CYCLESI2CSlave_Definitions.h0x0000 - 0xFFFF

The I2C slave transfer timeout cycles specifies the timeout period for any I2C transaction, in units of the function timer clock period. I2C transactions are timed from start condition to stop condition, or start condition to re-start condition.

I2C Slave Request Enable Definition

ParameterFileValid Values
I2CSLAVE__REQ_ENABLEI2CSlave_Definitions.htrue, false

The request enable controls whether the I2C request line feature is included in the driver build. The I2C request line provides a mechanism for the slave to signal the master that it would like to communicate.

I2C Slave Request Line Definition

ParameterFileValid Values
I2CSLAVE__REQ_POUTI2CSlave_Definitions.hPxOUT
I2CSLAVE__REQ_PDIRI2CSlave_Definitions.hPxDIR
I2CSLAVE__REQ_MASKI2CSlave_Definitions.hBIT0 - BIT7

The I2C slave request line is defined by three values- the port output register, the port direction register, and the pin mask. These do not need to be defined if the slave request enable is set to false.

Function Timer Enable Definition

ParameterFileValid Values
FUNCTIONTIMER__ENABLEFunctionTimer_Definitions.htrue, false

The function timer enable controls whether the function timer is included in the driver build. If the I2C slave timeout feature is not used, memory is conserved when the function timer is disabled (set to false).

Function Timer Peripheral Definition

ParameterFileValid Values
FUNCTIONTIMER__PERIPHERALFunctionTimer_Definitions.hTIMER_A0_BASE, TIMER_A1_BASE, TIMER_A2_BASE, TIMER_A3_BASE

The function timer peripheral stores the base address of the Timer_A instance that should be associated with the function timer. This instance must have at least two capture compare units (CCR0 and CCR1).

Function Timer Clock Source Definition

ParameterFileValid Values
FUNCTIONTIMER__CLOCKFunctionTimer_Definitions.hTASSEL__SMCLK, TASSEL__ACLK

The function timer clock determines the resolution of the function timer, as well as the maximum delay. Sources include SMCLK and ACLK. Note that if the clock source is changed, the effective function timer delay may change.

Function Timer Clock Divider Definition

ParameterFileValid Values
FUNCTIONTIMER__DIVIDERFunctionTimer_Definitions.hID__1, ID__2, ID__4, ID__8

The function timer clock divider divides down the source clock (which was specified above). Note that if the clock divider is changed, the effective function timer delay may change.

Function Timer Extended Clock Divider Definition

ParameterFileValid Values
FUNCTIONTIMER__EXDIVIDERFunctionTimer_Definitions.hTAIDEX_0 to TAIDEX_7

The function timer extended divider allows an additional second divider to be added in series with the standard divider. Note that if the extended clock divider is changed, the effective function timer delay may change.

Function Timer LPM Clear Definition

ParameterFileValid Values
FUNCTIONTIMER__LPM_CLEARFunctionTimer_Definitions.hLPM0_bits, LPM1_bits, LPM2_bits, LPM3_bits, LPM4_bits

The function timer LPM clear controls which LPM bits are cleared upon exit from a function called in the foreground by the function timer. This should be set to enable CPU wake-up after a timeout event. The function timer feature will never use these bits to enter into a low power mode- it will only use them to exit.

Run-time Driver Configuration Options

The driver's runtime configuration options are specified by populating a tI2CSlavePort structure in the application, and passing this structure to the driver when opening the driver. The tI2CSlavePort structures are discussed below.

MemberDescriptionValid Values
bool (*pbReceiveCallback)(uint16_t)pbReceiveCallback is a function pointer that may point to a receive event handler in the application. If no receive handling is required, initialize this member to 0 (null).Null, or a valid function address.
void (*pvErrorCallback)(uint8_t)pvErrorCallback is a function pointer that may point to an error event handler in the application. If no error handling is required, initialize this member to 0 (null).Null, or a valid function address
ui16ReceiveBufferSizeThis member stores the size of the receive buffer pointed to by pReceiveBuffer.0x00 to 0xFF
pReceiveBufferThis member is a pointer to the I2C receive buffer.A valid pointer
bSendReadLengthFirstWhen set to true, this flag configures the driver to always load the length of the transmit buffer as the first data byte read by the bus master. This is useful when variable length bulk packets are being read out by the master, and the master needs to know how many bytes to read from the slave.true, false

If the timeout feature is included in the driver build, a function timer run-time configuration also happens- but it is handled automatically by the driver when the I2C port is opened. The function timer runtime configuration structure (tFunctionTimer) is outlined below for completeness, but application does not need to know any of the function timer details.

MemberDescriptionValid Values
ui16FunctionDelay_AThis member specifies the length of the delay (in timer clock cycles) before function A is called.0x0000 to 0xFFFF
bool (*pbFunction_A)(void)pbFunction_A is a function pointer to function A.A valid function pointer, else null.
ui16FunctionDelay_BThis member specifies the length of the delay (in timer clock cycles) before function B is called.0x0000 to 0xFFFF
bool (*pbFunction_B)(void)pbFunction_B is a function pointer to function B.A valid function pointer, else null.

Using the Driver: Opening and Closing the Driver

Opening and closing of the I2C slave driver is accomplished through the following function calls:

DescriptionDeclaration
Open the I2C Slave Portextern void I2CSlave_openPort(const tI2CSlavePort *pPort)
Close the I2C Slave Portextern void I2CSlave_closePort(void)

The driver is initialized and made ready for use by placing a call to I2CSlave_openPort(), which is passed a completed tI2CSlavePort structure. The tI2CSlavePort structure must be populated by the application. This structure is not modified by the driver at any time, and as such, it may be placed in read-only memory such as a C const memory section. It is important that the structure be left in memory at the original address that is passed to I2CSlave_openPort(), as the driver will reference this structure to access callback functions when the port is open. If the memory must be freed, first close the driver with a call to I2CSlave_closePort(). A call to I2CSlave_closePort() will disable the driver and its associated eUSCI_B peripheral, halting all I2C slave activity. If the timeout feature was included in the build, I2C_closePort() disables the function timer module that drives the timeout feature. After the port is closed, the event handlers will no longer be called and the tI2CSlave structure memory may be released to the application.

Open the I2C Slave Port

//
// Open the I2C slave port
//
I2CSlave_openPort(&g_myI2CPort);

Using the Driver: Transmitting Data (I2C Read Operation)

Data transmission is accomplished through the following function calls:

DescriptionDeclaration
Set Transmit Bufferextern void I2CSlave_setTransmitBuffer(uint8_t *pBuffer, uint16_t ui16Length)
Set Request Flagextern void I2CSlave_setRequestFlag(void)
Get Port Statusextern uint8_t I2CSlave_getPortStatus(void)

An I2C slave cannot push data out onto the bus on its own- rather, a master must address the slave and clock out the data. Therefore, the data buffer must be made available to the driver before the bus master attempts to read it. The application may pass a pointer and a length (in bytes) to the I2CSlave_setTransmitBuffer() function. This function will block if a current transaction is in progress, and will return when the transmit buffer pointer and length have been updated. If no transmit buffer is provided and the bus master attempts to read from the slave, it will read out the invalid character (a compile-time option).

The I2CSlave_setTransmitBuffer() function does not perform a copy of the buffer- rather, it just stores its location and length. This is very valuable for "register" applications, where the application just updates the buffer, and I2CSlave_setTransmitBuffer() only needs to be called once during initialization. Repeated reads from the bus master will re-read the same buffer until I2CSlave_setTransmitBuffer() is called again. On the flipside, it is important that the application does not overwrite the transmit buffer space until transmission is complete.

If the slave request feature is enabled, the master may be signaled by calling the I2CSlave_setRequestFlag() function. This function immediately pulls the request line, then waits for the master to begin performing a read operation before it returns (or times out).

The driver state may be obtained by the application at any time by calling I2CSlave_getPortStatus(). This function returns the current I2C Slave state. The possible states are enumerated by tI2CSlaveStates. The possible enumerations are listed below.

Port Status OptionDescription
eI2CSlaveIsClosedThe driver is closed. All functions besides I2CSlave_openPort() and I2CSlave_getPortStatus() are invalid.
eI2CSlaveIsIdleThe driver is open, but no I2C transactions are currently in progress.
eI2CSlaveIsBeingReadThe driver is open and the bus master is reading data. The driver loads data from the transmit buffer until all data has been sent, then it loads the invalid byte.
eI2CSlaveIsBeingWrittenToThe driver is open and the bus master is writing data. The written data is placed into the receive buffer if there is space. At the end of this state, the receive callback in the application is called.

Setting the I2C Slave Transmit Buffer

uint8_t g_ui8MemoryArray[MEMORY_SIZE] = {0};
I2CSlave_setTransmitBuffer(g_ui8MemoryArray, MEMORY_SIZE);

Using the Driver: Receiving Data (I2C Write Operation)

The bus master may start a write operation at any time. The driver will buffer incoming data into the receive buffer. When the transaction is complete, the application receive callback is called. The callback is passed the size of the data received from the master (in bytes). Since it is called from the driver interrupt service routine, no other interrupts will be processed and the eUSCI_B may be stretching the bus clock line until the driver returns from the receive callback. This provides the callback function the opportunity to process any received data and update the transmit buffer before the master may continue with communication.

Using the Driver: Error Handling

The driver provides an error callback to alert the application if there is a problem with the driver. The error callback, when called, passes a value indicating the error that occurred. The possible errors are listed below.

Error CodeDescription
eI2CSlaveTransmitRequestTimeoutThis code indicates that a slave request to the bus master was not serviced within the timeout window, and the request timed out.
eI2CSlaveTransactionTimeoutThis code indicates that an I2C transaction was taking longer than the timeout window, and the transaction timed out. This error also results in a driver reset.
eI2CSlaveReceiveBufferFullThis code indicates that the receive buffer was full during the last read transaction, and data from the bus master was lost.
eI2CSlaveWasReadBeyondTransmitBufferThis code indicates that the bus master attempted to read data from the slave beyond the valid transmit buffer length (indicating the master was reading invalid bytes).

Using the Driver: Example Application

This is an I2C Slave Driver 512B Embedded Memory IC example. This example project configures the MSP430FR2633 as a 512B RAM device with an I2C interface. The master may write up to 128 bytes to the 8kB memory at a time. The master may read from any memory address until the end of the memory section at any time. The read/write format is as follows: I2C-START / ADDRESS+RW / MEMORY ADDR UPPER BYTE / MEMORY ADDR LOWER BYTE / DATA.

#include "driverlib.h"
#include "I2CSlave.h"
#include "string.h"
//
// MEMORY_SIZE defines the size of the memory array in bytes
//
#define MEMORY_SIZE (512)
//
// g_ui8MemoryArray is the memory array.
//
uint8_t g_ui8MemoryArray[MEMORY_SIZE] = {0};
//
// I2C_RECEIVE_BUFFER_SIZE defines the size of the I2C receive buffer
//
#define I2C_RECEIVE_BUFFER_SIZE 130
//
// g_ui8I2CReceiveBuffer is the buffer space used for I2C receive ops.
//
uint8_t g_ui8I2CReceiveBuffer[I2C_RECEIVE_BUFFER_SIZE];
//
// REQ_LOW_ADDR defines a macro to get the low address byte in the I2C
// receive buffer.
//
// REQ_HIGH_ADDR defines a macro to get the high address byte in the I2C
// receive buffer.
//
// REQ_FULL_ADDR defines a macro to access the full 13-bit address in
// the receive buffer
//
// REMAINING_MEMORY defines macro to get the remaining memory from the
//current address in the receive buffer to the end of memory.
//
// DATA_TO_WRITE defines a macro to get a pointer to the data to write
// in the I2C receive register
//
#define REQ_LOW_ADDR (g_ui8I2CReceiveBuffer[1])
#define REQ_HIGH_ADDR (g_ui8I2CReceiveBuffer[0])
#define REQ_FULL_ADDR ((uint16_t)REQ_LOW_ADDR | ((uint16_t)REQ_HIGH_ADDR << 8))
#define REMAINING_MEMORY (MEMORY_SIZE-REQ_FULL_ADDR)
#define DATA_TO_WRITE (&g_ui8I2CReceiveBuffer[2])
//
// The receive handler is called by the I2C port driver whenever a new
// packet was received.
//
bool receiveHandler(uint16_t ui16Length)
{
//
// Update the current memory location
//
uint8_t *pCurrMemoryLocation = &g_ui8MemoryArray[REQ_FULL_ADDR];
I2CSlave_setTransmitBuffer(pCurrMemoryLocation, REMAINING_MEMORY);
//
// If there is data in the packet, write it into the memory
//
if (ui16Length > 2)
{
memcpy(pCurrMemoryLocation, DATA_TO_WRITE, (ui16Length-2));
}
//
// No need to wake the CPU, return false to exit asleep
//
return false;
}
void errorHandler(uint8_t ui8Error)
{
return;
}
//
// g_myI2CPort specifies the I2C Slave port configuration that is passed
// to I2CSlave_openPort().
//
const tI2CSlavePort g_myI2CPort =
{
.pbReceiveCallback = &receiveHandler,
.pvErrorCallback = &errorHandler,
.ui16ReceiveBufferSize = I2C_RECEIVE_BUFFER_SIZE,
.pReceiveBuffer = g_ui8I2CReceiveBuffer,
.bSendReadLengthFirst = false
};
void main(void)
{
WDT_A_hold(WDT_A_BASE);
PMM_unlockLPM5();
//
// Set P1.6 and P1.7 to UCB0SDA and UCB0SCL, respectively
//
GPIO_setAsPeripheralModuleFunctionOutputPin(GPIO_PORT_P1, GPIO_PIN2, GPIO_SECONDARY_MODULE_FUNCTION);
GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_P1, GPIO_PIN3, GPIO_SECONDARY_MODULE_FUNCTION);
//
// Enable masked interrupts
//
__bis_SR_register(GIE);
//
// Open the I2C slave port
//
I2CSlave_openPort(&g_myI2CPort);
I2CSlave_setTransmitBuffer(g_ui8MemoryArray, MEMORY_SIZE);
//
// Application background loop
//
while(1)
{
//
// Enter LPM3
//
LPM3;
}
}