Chapter 6 Addendum: Input Modes Explained - Part 1

by <a href="http://community.silabs.com/t5/Welcome-and-Announcements/Community-Ranking-System-and-Recognition-Program/m-p/140490#U140490"><font color="#000000"><font size="2">Ninja</font></font> </a> lynchtron on ‎04-25-2017 05:27 PM

ch6_addendum.png

 

NOTE: This is an addendum to chapter 6, so it appears out-of-order on the blog.  It covers input modes that may not be covered anywhere else in the blog series. 

 

So far, we have only used the digital input mode of the GPIO peripheral to detect a button press.  In those cases, the pin was used to provide information if the button was pressed or not pressed.  A voltage was present or it wasn’t present.  In this chapter, we will use other peripherals that are capable of detecting input signals, including analog voltages and pulses.  We can use these inputs to measure an external voltage source, or the light intensity of a light source, for example.  There are digital input peripherals that go beyond simple edge detection and can be used to count pulses, which can then be used to tell us the position or speed of rotation of a shaft.

 

This chapter aims to give you a little background into the many different ways your EFM32 MCU can sample input values.  You will learn how to pick the right peripheral for the task at hand.

Note that many of the peripherals in the EFM32 family include input pins such as I2C, USART, UART, etc.  Those peripherals are covered elsewhere, but keep in mind that is possible to use just the input pin from, say, a USART (the RX pin) and not make any use of the TX pin.  In addition, there are specialized input methods for OPAMP and LESENSE that are beyond the scope of this chapter.

 

Setup Your Project for Serial Wire Output (SWO) Console Display

Before we start experimenting with input modes, let’s set up a debug console to view the results of our experiments.  In the prior chapters, the only way to see evidence of anything happening on the Wonder Gecko Starter Kit as a result of our code was to blink an LED or examine the values of variables in Simplicity Studio’s watch window.   There is, however, a built-in console in the Simplicity Studio IDE that can make use of the standard debugging interface that you use to transfer programs to the EFM32.  This is called Serial Wire Output (SWO) and it uses the serial port protocol.  We don’t need to dive into how serial communication works in this chapter.  We will simply make use of it for displaying simple debug messages to the Simplicity Studio console through standard C library printf statements.

 

The following code can be used in any project to send printf statements to the Simplicity Studio IDE output console.

 

#include "em_device.h"
#include "em_chip.h"
#include <stdio.h>
 
int _write(int file, const char *ptr, int len)
{
    int x;
    for (x = 0; x < len; x++)
    ITM_SendChar (*ptr++);
    return (len);
}
 
void SWO_SetupForPrint(void) {
    /* Enable GPIO clock. */
    CMU ->HFPERCLKEN0 |= CMU_HFPERCLKEN0_GPIO;
    /* Enable Serial wire output pin */
    GPIO ->ROUTE |= GPIO_ROUTE_SWOPEN;
    #if defined(_EFM32_GIANT_FAMILY) || defined(_EFM32_LEOPARD_FAMILY) ||         
defined(_EFM32_WONDER_FAMILY) || defined(_EFM32_GECKO_FAMILY)         /* Set location 0 */         GPIO ->ROUTE = (GPIO ->ROUTE & ~(_GPIO_ROUTE_SWLOCATION_MASK)) |    
GPIO_ROUTE_SWLOCATION_LOC0;         /* Enable output on pin - GPIO Port F, Pin 2 */         GPIO ->P[5].MODEL &= ~(_GPIO_P_MODEL_MODE2_MASK);         GPIO ->P[5].MODEL |= GPIO_P_MODEL_MODE2_PUSHPULL;     #else         /* Set location 1 */         GPIO->ROUTE = (GPIO->ROUTE & ~(_GPIO_ROUTE_SWLOCATION_MASK)) |
GPIO_ROUTE_SWLOCATION_LOC1;         /* Enable output on pin */         GPIO->P[2].MODEH &= ~(_GPIO_P_MODEH_MODE15_MASK);         GPIO->P[2].MODEH |= GPIO_P_MODEH_MODE15_PUSHPULL;     #endif     /* Enable debug clock AUXHFRCO */     CMU ->OSCENCMD = CMU_OSCENCMD_AUXHFRCOEN;     /* Wait until clock is ready */     while (!(CMU ->STATUS & CMU_STATUS_AUXHFRCORDY)) ;     /* Enable trace in core debug */     CoreDebug ->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;     ITM ->LAR = 0xC5ACCE55;     ITM ->TER = 0x0;     ITM ->TCR = 0x0;     TPI ->SPPR = 2;     TPI ->ACPR = 0xf;     ITM ->TPR = 0x0;     DWT ->CTRL = 0x400003FE;     ITM ->TCR = 0x0001000D;     TPI ->FFCR = 0x00000100;     ITM ->TER = 0x1; } int main(void) {     SWO_SetupForPrint();       printf("hello world!\n");       while (1)     {     } }

Note that printf formatting (%f) is not supported by default.  To enable it, you must check the box "print floats" in Project > Properties > C/C++ Build > Settings >  GNU ARM Linker > General.

Also, note that the SWO console will not display anything until it finds a “\n” in your message string!

 

Keep in mind that you should use the printf statement sparingly.  The statements take time to run and it can disrupt the timing in your code.  Use them only during basic debugging and only during sections of code that have no timing constraints.
 
General Purpose Input Output (GPIO)

6_gpio.png

The most obvious peripheral in the MCU that can sample input pins is the GPIO.  The GPIO input modes can detect if a voltage is closer to ground or VCC and can trigger interrupts on positive or negative edges.  There are filters available to clean up glitches, and pullups and pulldowns to bias the pin high or low in the absence of external drivers.  The GPIO is the easiest input peripheral to configure and use, but should only be used for DC or low-frequency signals, perhaps under 1kHz or so.  Sampling the GPIO constantly consumes power, and interrupting the MCU on every edge requires software to take action, which keeps your software busy.  There are hardware peripherals that can automate the faster signals for you, which is what we will learn about in this chapter.

 

The following is all that is required to start sampling the state of external GPIO pin PA1.  Note that there are pullups, pulldowns, and filters available for the input mode that can be specified instead of gpioModeInput.

 

CMU_ClockEnable(cmuClock_GPIO, true);
GPIO_PinModeSet(gpioPortA, 0, gpioModeInput, 0);
bool input_state = GPIO_PinInGet(gpioPortA, 1);

More details about the GPIO peripheral can be found in Application Note AN0012 General Purpose Input Output.


Analog to Digital Converter (ADC)

6_adc.png

While the GPIO is a digital input pin, generating a value of either 0 or 1, the Analog to Digital Converter (ADC) can differentiate 4096 digital levels between ground and VCC, thanks to its 12-bit resolution.  The The ADC is available in all models of the EFM32 family of MCU’s including the Wonder Gecko.  (There are a few of the low-pin-count models that omit the ADC.)  The ADC translates the voltage present on the input pin into a digital value at a rate up to 1 million samples per second.  The ADC can therefore sample complex waveforms such as audio for processing by the MCU. 

 

There are eight input channels that can be connected to the ADC on the Wonder Gecko, each of which must be used one at a time.  Some inputs can be used in a differential manner, where one pin is the positive input and another ADC pin is the negative input, instead of using ground as the negative input. 

 

To demonstrate the operation of the ADC, we can use the ambient light sensor on the Starter Kit.  The light sensor is a transistor with the base voltage controlled by light, and it varies between 0V and 2.5V with a pulldown to ground through a 22k-ohm resistor.

6_light_sense.png

The light sensor is connected to the EFM32 on the starter kit to pins PD6 (for excitation, a DC voltage) and PC6 as an input to ACMP0.  (Keep this in mind whenever you are using these pins on the Starter Kit for another purpose!)  We can use a wire jumper to connect PC6 (pin 17 on the expansion header) to ADC channel 0 on PD0 and then measure the voltage generated by the light sensor in the ADC. 

6_adc_light_sensor.png
The following code configures the ADC for light sensing.

      CMU_ClockEnable(cmuClock_GPIO, true);
      CMU_ClockEnable(cmuClock_ADC0, true);
 
      // Set the sample rate of ADC0
      ADC_Init_TypeDef       init       = ADC_INIT_DEFAULT;
      init.timebase = ADC_TimebaseCalc(0);
      init.prescale = ADC_PrescaleCalc(7000000, 0);
      ADC_Init(ADC0, &init);
 
      // Set up the ADC0 on channel0 (PD0) single-ended, referenced to 2.5V internal reference
      // Note that PD0 must be wire jumpered to PC6, since the light sensor is connected to PC6 on the starter kit
      ADC_InitSingle_TypeDef sInit = ADC_INITSINGLE_DEFAULT;
      sInit.input = adcSingleInputCh0;
      sInit.reference = adcRefVDD;
      sInit.acqTime = adcAcqTime32;
      ADC_InitSingle(ADC0, &sInit);
 
      // Excite the light sensor on PD6
      GPIO_PinModeSet(gpioPortD, 6, gpioModePushPull, 1);

 

Note that using the VDD as a reference means that the ADC readings will change as VDD changes, for example, as your battery drains.  It would be better to measure against the 2.5V or 1.25V internal references which do not change as VDD voltage changes.  This would then require your input signal to be scaled with a voltage divider.  In our example on the Starter Kit, VDD will remain stable at 3.3V, so we don’t need to take that step.

 

Once the ADC is configured, we can set up a timer to trigger an interrupt once a second, which was first explained in chapter 5. We will restart this timer after every ADC sample.

#define ONE_SECOND_TIMER_COUNT            13672
CMU_ClockEnable(cmuClock_TIMER1, true);
 
      // Create a timerInit object, based on the API default
      TIMER_Init_TypeDef timerInit = TIMER_INIT_DEFAULT;
      timerInit.prescale = timerPrescale1024;
 
      TIMER_IntEnable(TIMER1, TIMER_IF_OF);
 
      // Enable TIMER0 interrupt vector in NVIC
      NVIC_EnableIRQ(TIMER1_IRQn);
 
      // Set TIMER Top value
      TIMER_TopSet(TIMER1, ONE_SECOND_TIMER_COUNT);
 
      TIMER_Init(TIMER1, &timerInit);
 
      // Wait for the timer to get going
      while (TIMER1->CNT == 0)
            ;

 

The TIMER1 interrupt handler must be implemented to stop the timer once it triggers an interrupt and stop the timer from counting further.  As a reminder, this small function can appear anywhere in your source code, since it is declared in the EFM32 libraries and implemented in your source code.

void TIMER1_IRQHandler(void)
{
      TIMER_IntClear(TIMER1, TIMER_IF_OF);
      TIMER1->CMD = TIMER_CMD_STOP;
}

 

Finally, we can pull it all together in the main function while loop.

 

      uint32_t value;
      uint32_t last_value = 9999; // Impossible ADC value
      uint32_t sample = 0;
      while (1)
      {
            // Start an ADC aquisition
            ADC_Start(ADC0, adcStartSingle);
 
            // Wait for the measurement to complete
            while ( ADC0->STATUS & ADC_STATUS_SINGLEACT);
 
            // Get the ADC value
            value = ADC_DataSingleGet(ADC0);
 
            // Only print if the value has changed
            if ((value + ADC_NOISE) < last_value || (value - ADC_NOISE) > last_value)
            {
                  float voltage = value * 3.3;
                  voltage /= 4096.0;
 
                  // NOTE: To get floating point printing to work, you must enable "print floats" check box in:
                  // Project > Properties > C/C++ Build > GNU ARM Linker > General
                  printf("Sample #%3d ADC:%4d Voltage:%3.2f\n", (int) sample, (int) value, voltage);
            }
 
            last_value = value;
            sample++;
 
            // Wait a second before the next ADC sample
            TIMER1->CNT = 0;
            TIMER1->CMD = TIMER_CMD_START;
 
            // Sleep until the timer expires.
            EMU_EnterEM1();
      }

When you run this code, you will first see the ambient light as expressed as an ADC reading and as a calculated voltage.  As a flashlight is brought to the light sensor, the reading will change to reflect the difference in light intensity.

 

Chapter 6: Input Modes!
Sample #  0 ADC: 538 Voltage:0.43  << Room ambient light condition
Sample #  4 ADC: 500 Voltage:0.40
Sample #  8 ADC:1250 Voltage:1.01  << Light approaching the sensor
Sample #  9 ADC:3241 Voltage:2.61
Sample # 10 ADC:3954 Voltage:3.19  << Light held close to sensor
Sample # 16 ADC:1166 Voltage:0.94
Sample # 17 ADC: 461 Voltage:0.37  << Back at ambient lighting

 

IMPORTANT NOTE: DO NOT connect any ADC pin to the 5V pin on the Wonder Gecko Starter Kit!  The pins on the EFM32 are not capable of tolerating values higher than VCC + 0.3V.  If you input a voltage higher than that, you can permanently damage the input pin.  There is a “5V Reference” in the differential mode of the ADC, but this does not mean that the voltage on a single pin can exceed VCC!

 

In the test above, the ADC was only called on to perform one sample per second.   However, the ADC is capable of up to 1 million samples per second, and can therefore be used to perform complex signal analysis on analog signals.  Those techniques are beyond the scope of this chapter.

 

To save power during sleep states, the ADC needs to be stopped before entering energy modes EM2 or EM3 with the WARMUPMODE bit cleared in the ADC CTRL register.

 

More details about the ADC peripheral can be found in Application Note AN0021 Analog to Digital Converter.

 

In the next section, we will configure the Analog Comparator (ACMP) to level-shift the detected LED pulse from a weak signal to one that can be used as normal input logic to the MCU.