Driver for the capacitive sense slider available on the EFM32 Giant, Wonder and Leopard starter kits.

Dependents:   EFM32 RDA5807M RDS Radio EMF32-Segment-Touch-Demo EFM32_Bugs MFALHIMOHAMMED ... more

Information

All examples in this repo are considered EXPERIMENTAL QUALITY, meaning this code has been created as one-off proof-of-concept and is suitable as a demonstration for experimental purposes only. This code will not be regularly maintained by Silicon Labs and there is no guarantee that these projects will work across all environments, SDK versions and hardware.

/media/uploads/stevew817/screenshot_2015-03-17_13.40.06.png

caplesense.c

Committer:
Steven Cooreman
Date:
2015-05-04
Revision:
3:8d096e5bc045
Parent:
0:459a1af84a64

File content as of revision 3:8d096e5bc045:

/**************************************************************************//**
 * @file
 * @brief Capacitive sense driver
 * @version 3.20.9
 ******************************************************************************
 * @section License
 * <b>(C) Copyright 2014 Silicon Labs, http://www.silabs.com</b>
 *******************************************************************************
 *
 * This file is licensensed under the Silabs License Agreement. See the file
 * "Silabs_License_Agreement.txt" for details. Before using this software for
 * any purpose, you must agree to the terms of that agreement.
 *
 ******************************************************************************/




/* EM header files */
#include "em_device.h"

/* Drivers */
#include "caplesense.h"
#include "em_emu.h"
#include "em_acmp.h"
#include "em_assert.h"
#include "em_cmu.h"
#include "em_emu.h"
#include "em_gpio.h"
#include "em_int.h"
#include "em_lesense.h"

/* Capacitive sense configuration */
//#include "caplesenseconfig.h"
// ^^ Already included through caplesense.h ^^

/**************************************************************************//**
 * @brief This vector stores the latest read values from LESENSE
 * @param LESENSE_CHANNELS Vector of channels.
 *****************************************************************************/
static volatile uint32_t channelValues[LESENSE_CHANNELS] =
{
/*  Ch0,   Ch1,   Ch2,   Ch3,   Ch4,   Ch5,   Ch6,   Ch7    */
  0, 0, 0, 0, 0, 0, 0, 0,
/*  Ch8,   Ch9,   Ch10,  Ch11,  Ch12,  Ch13,  Ch14,  Ch15   */
  0, 0, 0, 0, 0, 0, 0, 0
};


/**************************************************************************//**
 * @brief  This stores the maximum values seen by a channel
 * @param LESENSE_CHANNELS Vector of channels.
 *****************************************************************************/
static volatile uint32_t channelMaxValues[LESENSE_CHANNELS] =
{
/*  Ch0,   Ch1,   Ch2,   Ch3,   Ch4,   Ch5,   Ch6,   Ch7    */
  1, 1, 1, 1, 1, 1, 1, 1,
/*  Ch8,   Ch9,   Ch11,  Ch11,  Ch12,  Ch13,  Ch14,  Ch15   */
  1, 1, 1, 1, 1, 1, 1, 1
};

/**************************************************************************//**
 * @brief  A bit vector which represents the channels to iterate through
 * @param LESENSE_CHANNELS Vector of channels.
 *****************************************************************************/
static const bool channelsInUse[LESENSE_CHANNELS] = LESENSE_CAPSENSE_CH_IN_USE;
static bool init = true;

/**************************************************************************//**
 * Prototypes
 *****************************************************************************/
void CAPLESENSE_setupCMU(void);
void CAPLESENSE_setupGPIO(void);
void CAPLESENSE_setupACMP(void);


/**************************************************************************//**
 * Local variables
 *****************************************************************************/
/** Callback function for LESENSE interrupts. */
static void (*lesenseScanCb)(void);
/** Callback function for LESENSE interrupts. */
static void (*lesenseChCb)(void);

/** The current channel we are sensing */
static volatile uint8_t currentChannel;



/**************************************************************************//**
 * @brief  Setup the CMU
 *****************************************************************************/
void CAPLESENSE_setupCMU(void)
{
  /* Ensure core frequency has been updated */
  SystemCoreClockUpdate();

  /* Enable HF peripheral clock. */
  CMU_ClockEnable(cmuClock_HFPER, 1);
  /* Enable clock for GPIO. */
  CMU_ClockEnable(cmuClock_GPIO, 1);
  /* Enable clock for ACMP0. */
  CMU_ClockEnable(cmuClock_ACMP0, 1);
  /* Enable clock for ACMP1. */
  CMU_ClockEnable(cmuClock_ACMP1, 1);
  /* Enable CORELE clock. */
  CMU_ClockEnable(cmuClock_CORELE, 1);
  /* Enable clock for LESENSE. */
  CMU_ClockEnable(cmuClock_LESENSE, 1);

  /* Enable clock divider for LESENSE. */
  CMU_ClockDivSet(cmuClock_LESENSE, cmuClkDiv_1);
}


/**************************************************************************//**
 * @brief  Setup the GPIO
 *****************************************************************************/
void CAPLESENSE_setupGPIO(void)
{
  /* Configure the drive strength of the ports for the light sensor. */
  GPIO_DriveModeSet(CAPLESENSE_SLIDER_PORT0, gpioDriveModeStandard);

  /* Initialize the 4 GPIO pins of the touch slider for using them as LESENSE
   * scan channels for capacitive sensing. */
  GPIO_PinModeSet(CAPLESENSE_SLIDER_PORT0, CAPLESENSE_SLIDER0_PIN, gpioModeDisabled, 0);
  GPIO_PinModeSet(CAPLESENSE_SLIDER_PORT0, CAPLESENSE_SLIDER1_PIN, gpioModeDisabled, 0);
  GPIO_PinModeSet(CAPLESENSE_SLIDER_PORT0, CAPLESENSE_SLIDER2_PIN, gpioModeDisabled, 0);
  GPIO_PinModeSet(CAPLESENSE_SLIDER_PORT0, CAPLESENSE_SLIDER3_PIN, gpioModeDisabled, 0);
}


/**************************************************************************//**
 * @brief  Setup the ACMP
 *****************************************************************************/
void CAPLESENSE_setupACMP(void)
{
  /* ACMP capsense configuration constant table. */
  static const ACMP_CapsenseInit_TypeDef initACMP =
  {
    .fullBias                 = false,
    .halfBias                 = false,
    .biasProg                 =                  0x7,
    .warmTime                 = acmpWarmTime512,
    .hysteresisLevel          = acmpHysteresisLevel7,
    .resistor                 = acmpResistor0,
    .lowPowerReferenceEnabled = false,
    .vddLevel                 =                 0x3D,
    .enable                   = false
  };


  /* Configure ACMP locations, ACMP output to pin disabled. */
  ACMP_GPIOSetup(ACMP0, 0, false, false);
  ACMP_GPIOSetup(ACMP1, 0, false, false);

  /* Initialize ACMPs in capacitive sense mode. */
  ACMP_CapsenseInit(ACMP0, &initACMP);
  ACMP_CapsenseInit(ACMP1, &initACMP);

  /* Don't enable ACMP, LESENSE controls it! */
}


/**************************************************************************//**
 * @brief  Setup the LESENSE for capavitive sensing
 * @param sleep If true, go into sleep mode.
 *****************************************************************************/
void CAPLESENSE_setupLESENSE(bool sleep)
{
  uint8_t     i;

  /* Array for storing the calibration values. */
  static uint16_t capsenseCalibrateVals[4];

  /* LESENSE channel configuration constant table in sense mode. */
  static const LESENSE_ChAll_TypeDef initChsSense = LESENSE_CAPSENSE_SCAN_CONF_SENSE;
  /* LESENSE channel configuration constant table in sleep mode. */
  static const LESENSE_ChAll_TypeDef initChsSleep = LESENSE_CAPSENSE_SCAN_CONF_SLEEP;
  /* LESENSE central configuration constant table. */
  static const LESENSE_Init_TypeDef  initLESENSE =
  {
    .coreCtrl         =
    {
      .scanStart    = lesenseScanStartPeriodic,
      .prsSel       = lesensePRSCh0,
      .scanConfSel  = lesenseScanConfDirMap,
      .invACMP0     = false,
      .invACMP1     = false,
      .dualSample   = false,
      .storeScanRes = false,
      .bufOverWr    = true,
      .bufTrigLevel = lesenseBufTrigHalf,
      .wakeupOnDMA  = lesenseDMAWakeUpDisable,
      .biasMode     = lesenseBiasModeDutyCycle,
      .debugRun     = false
    },

    .timeCtrl         =
    {
      .startDelay     =          0U
    },

    .perCtrl          =
    {
      .dacCh0Data     = lesenseDACIfData,
      .dacCh0ConvMode = lesenseDACConvModeDisable,
      .dacCh0OutMode  = lesenseDACOutModeDisable,
      .dacCh1Data     = lesenseDACIfData,
      .dacCh1ConvMode = lesenseDACConvModeDisable,
      .dacCh1OutMode  = lesenseDACOutModeDisable,
      .dacPresc       =                        0U,
      .dacRef         = lesenseDACRefBandGap,
      .acmp0Mode      = lesenseACMPModeMuxThres,
      .acmp1Mode      = lesenseACMPModeMuxThres,
      .warmupMode     = lesenseWarmupModeNormal
    },

    .decCtrl          =
    {
      .decInput  = lesenseDecInputSensorSt,
      .chkState  = false,
      .intMap    = true,
      .hystPRS0  = false,
      .hystPRS1  = false,
      .hystPRS2  = false,
      .hystIRQ   = false,
      .prsCount  = true,
      .prsChSel0 = lesensePRSCh0,
      .prsChSel1 = lesensePRSCh1,
      .prsChSel2 = lesensePRSCh2,
      .prsChSel3 = lesensePRSCh3
    }
  };

  /* Only initialize main LESENSE parameters once. */
  if (init)
  {
    /* Initialize LESENSE interface with RESET. */
    LESENSE_Init(&initLESENSE, true);
  }

  /* Different configuration for "sleep" and "sense" modes. */
  if (sleep)
  {
    /* Stop LESENSE before configuration. */
    LESENSE_ScanStop();

    /* Wait until the currently active scan is finished. */
    while (LESENSE_STATUS_SCANACTIVE & LESENSE_StatusGet()) ;

    /* Clear result buffer. */
    LESENSE_ResultBufferClear();

    /* Set scan frequency (in Hz). */
    (void) LESENSE_ScanFreqSet(0U, 4U);

    /* Set clock divisor for LF clock. */
    LESENSE_ClkDivSet(lesenseClkLF, lesenseClkDiv_1);

    /* Configure scan channels. */
    LESENSE_ChannelAllConfig(&initChsSleep);

    /* Restore calibration values. */
    LESENSE_ChannelThresSet(CAPLESENSE_SLIDER0_PIN, CAPLESENSE_ACMP_VDD_SCALE, capsenseCalibrateVals[0]);
    LESENSE_ChannelThresSet(CAPLESENSE_SLIDER1_PIN, CAPLESENSE_ACMP_VDD_SCALE, capsenseCalibrateVals[1]);
    LESENSE_ChannelThresSet(CAPLESENSE_SLIDER2_PIN, CAPLESENSE_ACMP_VDD_SCALE, capsenseCalibrateVals[2]);
    LESENSE_ChannelThresSet(CAPLESENSE_SLIDER3_PIN, CAPLESENSE_ACMP_VDD_SCALE, capsenseCalibrateVals[3]);

    /* Disable scan complete interrupt. */
    LESENSE_IntDisable(LESENSE_IEN_SCANCOMPLETE);
  }
  else
  {
    /* Stop LESENSE before configuration. */
    LESENSE_ScanStop();

    /* Wait until the currently active scan is finished. */
    while (LESENSE_STATUS_SCANACTIVE & LESENSE_StatusGet()) ;

    /* Clean scan complete interrupt flag. */
    LESENSE_IntClear(LESENSE_IEN_SCANCOMPLETE);

    /* Clear result buffer. */
    LESENSE_ResultBufferClear();

    /* Set scan frequency (in Hz). */
    (void) LESENSE_ScanFreqSet(0U, 5U);

    /* Set clock divisor for LF clock. */
    LESENSE_ClkDivSet(lesenseClkLF, lesenseClkDiv_8);

    /* Configure scan channels. */
    LESENSE_ChannelAllConfig(&initChsSense);

    /* Enable scan complete interrupt. */
    LESENSE_IntEnable(LESENSE_IEN_SCANCOMPLETE);
  }

  /* Enable LESENSE interrupt in NVIC. */
  NVIC_SetPriority(LESENSE_IRQn, 2);
  NVIC_EnableIRQ(LESENSE_IRQn);

  /* Start scanning LESENSE channels. */
  LESENSE_ScanStart();

  /* Run it only once. */
  if (init)
  {
    /* Assuming that the pads are not touched at first, we can use the result as
     * the threshold value to calibrate the capacitive sensing in LESENSE. */
    init = false;

    /* Waiting for buffer to be full. */
    while (!(LESENSE->STATUS & LESENSE_STATUS_BUFHALFFULL)) ;

    /* Read out steady state values from LESENSE for calibration. */
    for (i = 0U; i < CAPLESENSE_NUMOF_SLIDERS; i++)
    {
      capsenseCalibrateVals[i] = LESENSE_ScanResultDataBufferGet(i) - CAPLESENSE_SENSITIVITY_OFFS;
    }

    /* Set calibration values. */
    LESENSE_ChannelThresSet(CAPLESENSE_SLIDER0_PIN, CAPLESENSE_ACMP_VDD_SCALE, capsenseCalibrateVals[0]);
    LESENSE_ChannelThresSet(CAPLESENSE_SLIDER1_PIN, CAPLESENSE_ACMP_VDD_SCALE, capsenseCalibrateVals[1]);
    LESENSE_ChannelThresSet(CAPLESENSE_SLIDER2_PIN, CAPLESENSE_ACMP_VDD_SCALE, capsenseCalibrateVals[2]);
    LESENSE_ChannelThresSet(CAPLESENSE_SLIDER3_PIN, CAPLESENSE_ACMP_VDD_SCALE, capsenseCalibrateVals[3]);
  }
}


/**************************************************************************//**
 * @brief  LESENSE callback setup
 * @param  scanCb Scan callback
 * @param  chCb Channel callback
 *****************************************************************************/
void CAPLESENSE_setupCallbacks(void (*scanCb)(void), void (*chCb)(void))
{
  lesenseScanCb = scanCb;
  lesenseChCb   = chCb;
}


/**************************************************************************//**
 * @brief  LESENSE interrupt handler
 *****************************************************************************/
void LESENSE_IRQHandler(void)
{
  uint32_t count;


  /* LESENSE scan complete interrupt. */
  if (LESENSE_IF_SCANCOMPLETE & LESENSE_IntGetEnabled())
  {
    LESENSE_IntClear(LESENSE_IF_SCANCOMPLETE);

    /* Iterate trough all channels */
    for (currentChannel = 0; currentChannel < LESENSE_CHANNELS; currentChannel++)
    {
      /* If this channel is not in use, skip to the next one */
      if (!channelsInUse[currentChannel])
      {
        continue;
      }

      /* Read out value from LESENSE buffer */
      count = LESENSE_ScanResultDataGet();

      /* Store value in channelValues */
      channelValues[currentChannel] = count;

      /* Update channelMaxValues */
      if (count > channelMaxValues[currentChannel])
      {
        channelMaxValues[currentChannel] = count;
      }
    }

    /* Call callback function. */
    if (lesenseScanCb != 0x00000000)
    {
      lesenseScanCb();
    }
  }

  /* LESENSE channel interrupt. */
  if (CAPLESENSE_CHANNEL_INT & LESENSE_IntGetEnabled())
  {
    /* Clear flags. */
    LESENSE_IntClear(CAPLESENSE_CHANNEL_INT);

    /* Call callback function. */
    if (lesenseChCb != 0x00000000)
    {
      lesenseChCb();
    }
  }
}


/**************************************************************************//**
 * @brief Get the channelValue for a sensor segment
 * @param capSegment
 * @return channel
 *****************************************************************************/
uint8_t  CAPLESENSE_getSegmentChannel(uint8_t capSegment)
{
  uint8_t channel;

  switch (capSegment)
  {
  case(0):
    channel = SLIDER_PART0_CHANNEL;
    break;
  case(1):
    channel = SLIDER_PART1_CHANNEL;
    break;
  case(2):
    channel = SLIDER_PART2_CHANNEL;
    break;
  default:
    channel = SLIDER_PART3_CHANNEL;
    break;
  }
  return channel;

}


/**************************************************************************//**
 * @brief Get the current channelValue for a channel
 * @param channel The channel.
 * @return The channelValue.
 *****************************************************************************/
uint32_t CAPLESENSE_getVal(uint8_t channel)
{
  return channelValues[channel];
}

/**************************************************************************//**
 * @brief Get the current normalized channelValue for a channel
 * @param channel The channel.
 * @return The channel value in range (0-256).
 *****************************************************************************/
uint32_t CAPLESENSE_getNormalizedVal(uint8_t channel)
{
  uint32_t max = channelMaxValues[channel];
  return (channelValues[channel] << 8) / max;
}



/**************************************************************************//**
 * @brief Get the position of the slider
 * @return The position of the slider if it can be determined,
 *         -1 otherwise.
 *****************************************************************************/
int32_t CAPLESENSE_getSliderPosition(void)
{
  int      i;
  int      minPos = -1;
  uint32_t minVal = 236; /* adjust it */
  /* Values used for interpolation. There is two more which represents the edges.
   * This makes the interpolation code a bit cleaner as we do not have to make special
   * cases for handling them */
  uint32_t interpol[6]      = { 255, 255, 255, 255, 255, 255 };
  uint32_t channelPattern[] = { 0,                        SLIDER_PART0_CHANNEL + 1,
                                SLIDER_PART1_CHANNEL + 1,
                                SLIDER_PART2_CHANNEL + 1,
                                SLIDER_PART3_CHANNEL + 1 };

  /* The calculated slider position. */
  int position;

  /* Iterate through the 4 slider bars and calculate the current value divided by
   * the maximum value multiplied by 256.
   * Note that there is an offset of 1 between channelValues and interpol.
   * This is done to make interpolation easier.
   */
  for (i = 1; i < CAPLESENSE_NUMOF_SLIDERS + 1; i++)
  {
    /* interpol[i] will be in the range 0-256 depending on channelMax */
    interpol[i]  = channelValues[channelPattern[i] - 1] << 8;
    interpol[i] /= channelMaxValues[channelPattern[i] - 1];
    /* Find the minimum value and position */
    if (interpol[i] < minVal)
    {
      minVal = interpol[i];
      minPos = i;
    }
  }
  /* Check if the slider has not been touched */
  if (minPos == -1)
    return -1;

  /* Start position. Shift by 4 to get additional resolution. */
  /* Because of the interpol trick earlier we have to substract one to offset that effect */
  position = (minPos - 1) << 4;

  /* Interpolate with pad to the left */
  position -= ((256 - interpol[minPos - 1]) << 3)
              / (256 - interpol[minPos]);

  /* Interpolate with pad to the right */
  position += ((256 - interpol[minPos + 1]) << 3)
              / (256 - interpol[minPos]);

  return position;
}


/**************************************************************************//**
 * @brief Send the capacative sense system to sleep mode.
 *****************************************************************************/
void CAPLESENSE_Sleep(void)
{
  /* Go to EM2 and wait for the measurement to complete. */
  EMU_EnterEM2(true);
}


/**************************************************************************//**
 * @brief Initializes the capacative sense system without LESENSE.
 * @param sleep If true, go into sleep mode.
 *****************************************************************************/
void CAPLESENSE_Init(bool sleep)
{
  /* Disable interrupts */
  INT_Disable();

  /* Setup CMU. */
  CAPLESENSE_setupCMU();
  /* Setup GPIO. */
  CAPLESENSE_setupGPIO();
  /* Setup ACMP. */
  CAPLESENSE_setupACMP();
  /* Setup LESENSE. */
  CAPLESENSE_setupLESENSE(sleep);

  /* Initialization done, enable interrupts globally. */
  INT_Enable();
}