In this chapter, we will explore the common task of detecting user input via a capacitive button.  In Chapter 12, we interfaced with a capacitive sense controller via I2C.  That controller chip did all the work for us, so all our software needed to do was read some registers to calculate the position of a finger touch on the touchscreen. 


In addition to touch, we will add a Passive Infrared (PIR) sensor that will detect the presence of a user to wake up the EFM32 from EM4 (deep sleep) state, illuminate an LED, and begin scanning the capacitive sense buttons for a touch input.  This example application could be used as the starting point to build a touch-enabled and motion-activated doorbell, which could then load sound files, display an image on a display, or perhaps send the request on to other hardware. 



Materials Needed for this Chapter

  • Adafruit PIR (motion) sensor

Capacitive Sensing Overview

As figure 2.1 from AN0040 Hardware Design for Capacitive Touch shows, a capacitive sensor is formed by observing the capacitive changes on a pad of conducting material when a human body nears the pad. The human body adds capacitance to the pad, and we can detect that change in capacitance, which registers as a touch event.



There are several ways to detect changes in capacitance from a microcontroller.  The way we will do it in this chapter is to build a clock oscillator with the pad as part of the circuit, and measure the change in capacitance through the change in frequency of the oscillator.  Note that the actual frequency we use is not important.  It is only the change in frequency that indicates a touch or non-touch.


The EFM32 Analog Comparator (ACMP) will be used with a hardware TIMER to measure the change in clock frequency over time.  There are helpful tools in the emlib software library and in ACMP hardware to set up the clock oscillator for our capacitive sensing needs.  We must use hardware to count the pulses because they run in a range of 100kHz to 1.5MHz, which are faster than we can count with a simple GPIO input trigger and software.  We must set up the ACMP to trigger a PRS event, that is then counted by a hardware timer.


The capacitive sensor overview is covered in Silicon Labs application notes AN0028 Low Energy Sensor Interface - Capacitive Sense and AN0040 Hardware Design for Capacitive Touch.  You should read those application notes for a detailed understanding of this topic. 


IMPORTANT NOTE: The latest version of AN0828 - Capacitive Sensing Library Overview and AN0829 - Capacitive Sensing Library Configuration Guide make use of the capacitive sensing library (CSLIB) for EFM32 devices but are not covered in this chapter.  Those application notes provide information about additional tools to help you characterize your capacitive sense application.

The touch slider that is built in to the Starter Kit is shown in Figure 5.3 of the Wonder Gecko Starter Kit User Guide.




There are four capacitance pads on the Starter Kit that comprise a slider.  These pads are attached to EFM32 pins PC8, PC8, PC19, and PC11.  We can still use these pins for other things because the touch pads simply add capacitance to each of these signals.  We need a very specific timing circuit to detect a touch, but these pads are not likely to impact other digital uses of these pins.


Programming the Capacitive Touch Interface


As a reminder, all completed code for this project can be found in Github here.


We will be using the ACMP to generate the clock oscillator that uses the pad capacitance as part of the circuit.  The ACMP will then generate interrupts with every comparison event (rising edge of the clock) that will be fed via PRS into the TIMER to count those pulses over a set period of time, which will allow us to compare frequency changes over time.  The following function does this operation  on a single button, the left-most slider on Pad 1 (PC8).


void setup_capsense()
      /* Use the default STK capacative sensing setup */
      ACMP_CapsenseInit_TypeDef capsenseInit = ACMP_CAPSENSE_INIT_DEFAULT;
      CMU_ClockEnable(cmuClock_HFPER, true);
      CMU_ClockEnable(cmuClock_ACMP1, true);
      /* Set up ACMP1 in capsense mode */
      ACMP_CapsenseInit(ACMP1, &capsenseInit);
      // This is all that is needed to setup PC8, or the left-most slider
      // i.e. no GPIO routes or GPIO clocks need to be configured
      ACMP_CapsenseChannelSet(ACMP1, acmpChannel0);
      // Enable the ACMP1 interrupt
      ACMP_IntEnable(ACMP1, ACMP_IEN_EDGE);
      // Wait until ACMP warms up
      while (!(ACMP1->STATUS & ACMP_STATUS_ACMPACT)) ;
      CMU_ClockEnable(cmuClock_PRS, true);
      CMU_ClockEnable(cmuClock_TIMER1, true);
      // Use TIMER1 to count ACMP events (rising edges)
      // It will be clocked by the capture/compare feature
      TIMER_Init_TypeDef timer_settings = TIMER_INIT_DEFAULT;
      timer_settings.clkSel = timerClkSelCC1;
      timer_settings.prescale = timerPrescale1024;
      TIMER_Init(TIMER1, &timer_settings);
      TIMER1->TOP  = 0xFFFF;
      // Set up TIMER1's capture/compare feature, to act as the source clock
      TIMER_InitCC_TypeDef timer_cc_settings = TIMER_INITCC_DEFAULT;
      timer_cc_settings.mode = timerCCModeCapture;
      timer_cc_settings.prsInput = true;
      timer_cc_settings.prsSel = timerPRSSELCh0;
      timer_cc_settings.eventCtrl = timerEventRising;
      timer_cc_settings.edge = timerEdgeBoth;
      TIMER_InitCC(TIMER1, 1, &timer_cc_settings);
      // Set up PRS so that TIMER1 CC1 can observe the event produced by ACMP1

The setup_capsense() function can be summarized as follows:

  • Enable the clocks to all peripherals HFPER, ACMP1, TIMER1, PRS
  • Configure the ACMP for capacitive sensing, i.e. create a clock oscillator
  • Wait for the ACMP to warm up 
  • Enable ACMP interrupts, such that rising edge events can be sent on via PRS
  • Configure the TIMER to be clocked by the capture feature, which in turn acts on events from the PRS channel that is tied to the ACMP interrupts

The comments in the code mention that ACMP and other analog peripherals have no route registers nor GPIO enable functions.  The physical pins are tied directly to the analog peripherals, so all you need to do is enable and configure the analog peripheral for those pins to become enabled and active.  You don’t even need to turn on the clock to the GPIO peripheral.  The datasheet states that these kinds of analog pins are specified as location zero, even though these interfaces “do not have alternate settings or a LOCATION bitfield. In these cases, the pinout is shown in the column corresponding to LOCATION 0.”

Once this function is called in the main() function, and setup_utilities() is called to set up the SysTick interrupt for the expired_ms() function to work, a while loop can be constructed to fetch the current count:


#define ACMP_PERIOD_MS  100
      // Setup the systick for 1ms interrupts
      while (1)
            // Clear the count
            count = 0;
            TIMER1->CMD = TIMER_CMD_START;
            // Start a timer based on systick
            int32_t timer = set_timeout_ms(ACMP_PERIOD_MS);
            while (!expired_ms(timer))
            // Now observe the count and reset
            TIMER1->CMD = TIMER_CMD_STOP;
            count = TIMER1->CNT;
            TIMER1->CNT = 0;

If you set a breakpoint on the last statement in the while loop just after count is assigned a value, you can observe the value of the count when there is a touch and there is not a touch by running the Simplicity Studio IDE one loop at a time.  Your count observations may vary, depending on humidity and other factors.  This is OK, as long as you are seeing a clear difference in the count between a touch and a no-touch loop.


Most of the work here is done by the ACMP, PRS and TIMER hardware (and SysTick to some extent.)  The only thing that the main loop is doing is starting the hardware timer, waiting 100ms, stopping and reading the count of the of the timer, and then resetting timer count and before the loop starts all over again.  Note that the ACMP is running the whole time, and we only sleep long enough to wait for the count to accumulate.


The application note AN0028 Low Energy Sensor Interface - Capacitive Sense performs this while loop over all four touch pads without any looping software.  Think of the LESENSE peripheral as your main software while loop built into a programmable hardware peripheral.  And since LESENSE has its own clock and counter, no TIMER is necessary at all.  In the software examples that are given for AN0028, the only thing to do in software is enable and configure ACMP for capacitive sense, as the LESENSE takes over from there, once it is also configured to do so.  The example uses all four touch pads to slide a message on the LCD screen left or right according to your finger position.  The use of LESENSE is beyond the scope of this chapter.


If you want to see the actual clock oscillator generated by ACMP, and to observe the change in frequency produced by your finger, add the following lines to your setup_capsense() function:


      // Configure ACMP1 output to a pin D7 at location 2
      CMU_ClockEnable(cmuClock_GPIO, true);
      GPIO_PinModeSet(gpioPortD, 7, gpioModePushPull, 0);
      ACMP_GPIOSetup(ACMP1, 2, true, false);


You can then view the internal ACMP clock oscillator directly on an oscilloscope and observe the frequency differences between a touch and a no-touch event.



In the next section, we will setup our computer with a simple graphing script in the Python computer language, so that we can use it to observe the capacitive sense count over time and characterize the performance of the button.

  • Blog Posts
  • Makers