Official Blog of Silicon Labs

    Publish
     
      • Chapter 6 Addendum: Input Modes Explained - Part 3

        lynchtron | 06/156/2017 | 05:06 PM

        ch6_addendum.png

        In the last section, the ACMP peripheral was used as an analog input device to level-shift the light detected from an LED on the light sensor.  In this section, we will configure the TIMER peripheral to receive input edges that can be used to decode a quadrature encoder.  We will also configure the PCNT as an input counter to count pulses from the blinking LED of the prior sections. 

         

        TIMER

        The TIMER peripheral was first covered in chapter 5 and used for measuring time or generating output waveforms, but it is also a versatile counter that is capable of capturing edges on the input pins to the MCU. 

         

        In another mode called Quadrature Decoder mode, the TIMER module can be used across two TIMER capture pins to measure the speed and direction of a wheel.  The quadrature mode can work in either X2 or X4 modes, which simply means that both rising and falling edges of one or both pins can trigger counting events. 

         

        To demonstrate the basic operation of the TIMER input mode, we will configure it to capture the direction and speed of a wheel using a quadrature encoder.  The ACZ11 mechanical encoder can be purchased on Mouser here, or you can find them on Amazon.  This one has 20 detents, which means that the rotation of the shaft provides a clicking feel and generates edges on the encoder pins with each click.  We can use such an encoder in X1, X2, or X4 modes.  The TIMER peripheral cannot count in X1 mode, but the PCNT module in the following section can only count in X1 mode. 

        ACZ11_SPL.jpg

        The ACZ11 has five pins: three pins on one side make up the encoder, the middle pin is ground, and the encoder mechanically connects the outside pins to this ground pin as the shaft rotates.  It is up to us to connect the outside pins to a pullup resistor so we can detect when the encoder is connecting a pin to ground.  The other two pins (on the other side of the encoder) make up a Single Pole Single Throw (SPST) switch when you push in on the knob, just like the volume/power knob on some automobile radios.

        6_quadrature_decoder_scope_setup.png

        When we rotate the encoder clockwise, edges on pin B lead pin A.  When we rotate counterclockwise, edges on pin A lead pin B. The width of the pulses is determined by the speed of rotation, and the signals are always at 50% duty cycle to one another. 

         

         The timer has three compare/capture pins, CC0, CC1, and CC2.  All of the pins can be used to clock the TIMER CNT field, but only CC0 and CC1 can be used to run the quadrature decoder.

        6_quadrature_waveforms.png
        No resistors are needed when using the encoder with the EFM32.  One of the input modes available on the GPIO peripheral is input mode with pullup, where the inputs provide ~40k ohm pullups to VCC.  This mode makes the connections easy.  Just connect one of the outside pins of the encoder to PD1, and the other to PD2.  These are TIMER0 location #3 compare and capture pins.

        6_quadrature_decoder_connections.png

        When TIMER is used as an input peripheral, it is counting incoming edges and not keeping track of time.  In quadrature mode, it is determining position or some fraction of rotation and not the rate of rotation.  To measure the rate of rotation, you need another timer to keep track of the sample period.

        First, we will re-use the one second timer and interrupt from the ADC section of this chapter.  TIMER1 will keep track of time and interrupt after one second.  Then, we will set up TIMER0 as a quadrature counter connected to PD1 and PD2.

         

        // This timer doesn't keep track of time, but edges on the input pins
        void setup_quadrature_timer()
        {
              CMU_ClockEnable(cmuClock_TIMER0, true);
              CMU_ClockEnable(cmuClock_GPIO, true);
         
              // Set CC0 channel defaults
              TIMER_InitCC_TypeDef timerCCInit = TIMER_INITCC_DEFAULT;
              timerCCInit.edge       = timerEdgeBoth;          // X4 mode
              timerCCInit.filter     = true;
         
              // Configure CC channel 0
              TIMER_InitCC(TIMER0, 0, &timerCCInit);
         
              // Configure CC channel 1 in exactly the same way
              TIMER_InitCC(TIMER0, 1, &timerCCInit);
         
              // Set TIMER0 defaults
              TIMER_Init_TypeDef timerInit = TIMER_INIT_DEFAULT;
              timerInit.mode       = timerModeQDec;      // Quadrature mode
              timerInit.quadModeX4 = false;             // X2
         
              // Configure TIMER0
              TIMER_Init(TIMER0, &timerInit);
         
              // Route the input pins to TIMER0 from Location 3 and enable CC0 and CC1 in the route
              TIMER0->ROUTE = (TIMER0->ROUTE & ~_TIMER_ROUTE_LOCATION_MASK) | TIMER_ROUTE_LOCATION_LOC3;
              TIMER0->ROUTE |= TIMER_ROUTE_CC0PEN;
              TIMER0->ROUTE |= TIMER_ROUTE_CC1PEN;
         
              // Enable the GPIO pins needed
         
              // CC0 on Location 3 is PD1
              GPIO_PinModeSet(gpioPortD, 1, gpioModeInputPullFilter, 1);
         
              // CC1 on Location 3 is PD2
              GPIO_PinModeSet(gpioPortD, 2, gpioModeInputPullFilter, 1);
        }

        I have enabled the filter on the CC0/CC1 configuration because I saw switch bounce on the signals on the mechanical encoder when viewing the output on the oscilloscope.

         

        After setting up the two timers, the main while resets the TIMERs on each loop, then goes to sleep until TIMER1 wakes the MCU up after one second.  If we set up a variable called detents as an int16_t, only the TIMER0->CNT value is needed to fetch both direction and the number of detents detected.  This is because TIMER0->CNT is a 16-bit register, and the quadrature counter counts backwards when the shaft is rotated counterclockwise, producing a negative count.  However, in the code, you can also see how to fetch the direction bit from TIMER0->STATUS, and the results are consistent between the two methods.

         

              while (1)
              {
                    // Reset the timers
                    TIMER0->CNT = 0;
                    TIMER1->CNT = 0;
         
                    // Sleep until the timer expires
                    EMU_EnterEM1();
         
                    // One second has passed, fetch the number of detents
                    int16_t detents = TIMER0->CNT;
         
                    // Correct for X2 to X1 mode
                    detents = detents / 2;
         
                    if (detents != 0)
                    {
                          // Fetch the direction
                          uint8_t direction = (TIMER0->STATUS & 0b10) >> 1;
         
                          // calculate the speed of rotation per minute, RPM
                          int16_t rpm = (detents * 60) / 20;
         
                          // Print out the message
                          if (direction == 1)
                          {
                                printf("Shaft Direction:CCW Detents:%d RPM:%d\n", detents, rpm);
                          }
                          else
                          {
                                printf("Shaft Direction:CW Detents:%d RPM:%d\n", detents, rpm);
                          }
                    }
              }

        I had to correct the count from X1 to X2 by dividing the TIMER0->CNT by 2 on every click of the encoder.   Note that you must do this calculation after TIMER->CNT has been converted to an int16_t or else you will lose the sign bit.  The check of detents != 0 ensures that we don’t see any output on the SWO debug printf console until there is rotation on the encoder.  The revolutions per minute (RPM) can be calculated by the number of detents times 60 seconds, divided by 20 detents per revolutions of the encoder.

         

        You should see something like the following on the SWO console when you run the code, first by hand, and then by using a cordless drill to turn the knob at the drill's fastest setting.

         

        Chapter 6: Input Modes!
        Shaft Direction:CW Detents:1 RPM:3        << Turning by hand once each direction
        Shaft Direction:CCW Detents:-1 RPM:-3
        Shaft Direction:CW Detents:7 RPM:21
        Shaft Direction:CW Detents:5 RPM:15
        Shaft Direction:CW Detents:9 RPM:27
        Shaft Direction:CW Detents:11 RPM:33      << Fastest able to turn by hand
        Shaft Direction:CW Detents:112 RPM:336   
        Shaft Direction:CW Detents:445 RPM:1335   << Using a ~1500 RPM drill
        Shaft Direction:CW Detents:480 RPM:1440
        Shaft Direction:CW Detents:480 RPM:1440
        Shaft Direction:CW Detents:186 RPM:558
        Shaft Direction:CCW Detents:-1 RPM:-3
        Shaft Direction:CCW Detents:-145 RPM:-435
        Shaft Direction:CCW Detents:-445 RPM:-1335  << Drill in reverse
        Shaft Direction:CCW Detents:-447 RPM:-1341
        Shaft Direction:CCW Detents:-120 RPM:-360
        Shaft Direction:CCW Detents:-1 RPM:-3

        My cordless drill lists the RPM spec at 1500 RPM, which is within 4% of what I measured in the clockwise direction, and 11% in the counterclockwise direction.  Since I have owned the drill for many years, this is within expectations.

         

        More details about the TIMER peripheral can be found in Application Note AN0014 TIMER.

         

        Pulse Counter (PCNT)

        The Pulse Counter (PCNT) counts and measures incoming pulses to the MCU, even while the core is in EM3 sleep state.  Rather than wake the MCU on every edge on a GPIO pin, the PCNT counts pulses until a specific count is reached, which then wakes up the MCU if the PCNT interrupt is enabled.   Most EFM32 models have an 8-bit PCNT counter, capable of counting up to 255, but some models have a 16-bit counter on PCNT0.

         

        The PCNT peripheral is a little less versatile than the TIMER peripheral, as it can only count either positive edges or negative edges, but not both in “single output oversampling” or “externally clocked single input” modes.  It can also work with quadrature encoders, but only in X1 mode.  It cannot operate in X2 or X4 quadrature modes.  However, it can keep counting all the way in EM3 energy mode if it is externally clocked by the pulses on the input pin, whereas the TIMER can only operate down to EM2 energy mode.

         

        To demonstrate the basic operation of the PCNT, we can re-use our blinking LED and ACMP output to feed that output to the PCNT and count the number of LED pulses over time.  Once the count is reached, we can use the PCNT to interrupt the system and print the time to the SWO console.

        6_pcnt.png
        To re-use our blinking LED from the earlier section, all we need to do is place a wire jumper between PE2 (ACMP0_O) and PD0, which is our S0 pin for PCNT2.  There are two pins available for each PCNT, each with several locations, but the function that we want for S0 in this case is to clock the PCNT counter.  The optional S1 signal modifies the count of the S0 signal (forward or backwards), so be careful how you connect these pins. 

         

        The following code configures PCNT’s S0 pin on PD0 to count in LFACLK oversampling mode.

         

        #define PCNT_OVERFLOW                     (LED_BLINK_RATE / 2) -1
        // Setup PCNT2 input on PD0, to count up to LED blink rate then generate interrupt
        void setup_PCNT()
        {
              /* Select LFRCO as clock source for LFA */
              CMU_ClockSelectSet(cmuClock_LFA, cmuSelect_LFRCO);
              CMU_ClockEnable(cmuClock_HFLE, true);     /* Enable renamed CORELE clock */
              CMU_ClockEnable(cmuClock_PCNT2, true);
              CMU_ClockEnable(cmuClock_GPIO, true);
         
              /* Set configuration for pulse counter */
              PCNT_Init_TypeDef pcntInit = PCNT_INIT_DEFAULT;
              pcntInit.mode       = pcntModeOvsSingle;  /* clocked by LFACLK */
              pcntInit.counter    = 0;                  /* Set initial value to 0 */
              pcntInit.top        = PCNT_OVERFLOW;      /* Set top to max value */
         
              /* Initialize Pulse Counter */
              PCNT_Init(PCNT2, &pcntInit);
         
              // Clear the interrupt flag
              pcnt_interrupt = false;
         
              /* Enable PCNT overflow interrupt */
              PCNT_IntEnable(PCNT2, PCNT_IF_OF);
         
              /* Enable PCNT2 interrupt vector in NVIC */
              NVIC_EnableIRQ(PCNT2_IRQn);
         
              /* Route PCNT2 input to location 0 -> PCNT2_S0IN on PD0 */
              PCNT2->ROUTE = PCNT_ROUTE_LOCATION_LOC0;
         
              // Enable the pin PD0 for the PCNT2_S0IN input
              GPIO_PinModeSet(gpioPortD, 0, gpioModeInput, 0);
         
              // Clear the count again, because enabling the already-high PD0 adds 1 to the count
              PCNT_CounterSet(PCNT2, 0);
        }

        Once the PCNT is configured, any pulse on the GPIO pin will trigger a count, even if the first pulse is provided simply by configuring the PD0 pin to enable the input mode if the PD0 pin happens to be at a high state when that occurs.  Therefore, the PCNT2 is reset to 0 after the GPIO mode is set to input.

        At the module level (outside the main function), we define a global variable called pcnt_interrupt.

        bool pcnt_interrupt = false;

        We then setup a PCNT2 interrupt handler that simply sets pcnt_interrupt to true whenever the interrupt occurs.

         

        void PCNT2_IRQHandler(void)
        {
              /* Clear PCNT0 overflow interrupt flag */
              PCNT_IntClear(PCNT2, PCNT_IF_OF);
         
              pcnt_interrupt = true;
        }

        Now, in the main while loop, the pcnt_interrupt flag can be monitored and a message can be printed whenever the PCNT interrupt is triggered.

         

            setup_ACMP();
            setup_elapsed_timer();
            setup_blink_timer();
            setup_PCNT();
         
            TIMER1->CNT = 0;
            TIMER0->CNT = 0;
         
              while (1)
              {
                    // Sleep until the timer expires
                    EMU_EnterEM1();
         
                    if (pcnt_interrupt)
                    {
                          printf("PCNT Interrupt! Elapsed CNT:%d\n", TIMER1->CNT);
                          pcnt_interrupt = false;
                          TIMER1->CNT = 0;
                    }
              }

        Since TIMER1 is being used to track time and TIMER0 is being used to blink the LED, they are synchronized to 0 (as much as possible) before the while loop begins, just so that the counts can be compared easily and we don’t miss any early edges.  TIMER1 is cleared after every pcnt_interrupt occurs so we can use the TIMER1 CNT value directly without requiring any overflow logic.

         

        If all goes well, you should see a listing of TIMER1 clock counts that occur between each PCNT2 interrupt.  Remember that you only have 16-bits on the TIMER peripheral, which means that it only counts from 0 to 65,535, and 8-bits (on most EFM32 models) for PCNT, which only counts from 0 to 255.  In this case, that works well because we expected to find 10 LED pulses in one second on PCNT, which is 13672 TIMER clock counts.  If your events take longer or your TIMER is configured with a lower divisor, you could run out of room in the CNT register before the required pulses arrive.

         

        Chapter 6: Input Modes!
        PCNT Interrupt! Elapsed CNT:13681
        PCNT Interrupt! Elapsed CNT:13669
        PCNT Interrupt! Elapsed CNT:13669
        PCNT Interrupt! Elapsed CNT:13669
        PCNT Interrupt! Elapsed CNT:13669
        PCNT Interrupt! Elapsed CNT:13670
        PCNT Interrupt! Elapsed CNT:13669
        PCNT Interrupt! Elapsed CNT:13669
        PCNT Interrupt! Elapsed CNT:13669
        PCNT Interrupt! Elapsed CNT:13670
        PCNT Interrupt! Elapsed CNT:13669
        PCNT Interrupt! Elapsed CNT:13669

        For some reason, the first loop had a little bit more error than subsequent loops, but the overall error of TIMER1’s 13672 clock count to PCNT’s 13669 to 13681 clocks between interrupts is only a 0.1% measurement error.

         

        In oversampling mode, the PCNT has a maximum count rate of the LFACLK/2, which means that it can work reliably up to 32kHz.  If your pulses are faster than that, you will need to run the PCNT from the external clock pulses directly on the S0 pin, rather than sample the S0 pin with the LFACLK.  When you go that route, the PCNT peripheral has no internal clock, and your PCNT configuration write cycles will have no effect until there are three pulses on the S0 pin.  To make things easier, you can do your configuration of PCNT first while running on the LFACLK, and then switch to external S0 clocking after the PCNT is configured.  In externally-clocked mode, the PCNT can operate at a frequency up to the maximum specified rate of the MCU core clock.

         

        The PCNT can be used to count objects passing by a sensor, where an LED or an infrared LED is emitted and detected by a light sensor.  When objects block the LED, the light sensor sees pulses/edges and the PCNT can then count the number of objects passing the sensor.  It can also be used to count revolutions of a wheel, either with the same light emitter/sensor or with current-inducing pulses created by magnets on the wheel that pass by a stationary sensor.

         

        More details about the PCNT peripheral can be found in Application Note AN0024 Pulse Counter.

         

        This wraps up our coverage of MCU input mechanisms.

      • Chapter 6 Addendum: Input Modes Explained - Part 2

        lynchtron | 05/128/2017 | 10:00 AM

        ch6_addendum.png

         

        In the last section, we introduced the GPIO as an input mode peripheral and the ADC for an analog detection of the input voltage.  In this section, we will configure the Analog Comparator (ACMP) to function as a discrete logic device, as both an input and output. 

         

        We will use the ACMP to level-shift the detected LED pulse of the last section from a weak signal to one that can be used as normal input logic to the MCU.


        Analog Comparator (ACMP)

        6_acmp.png

        The Analog Comparator (ACMP) compares two analog input values and outputs a digital high or low signal depending on whether the positive input is higher than the negative input.  The inputs can be a pair of pins on the MCU, or an internal, software-controlled reference value with 64 divisions of the reference for one of the inputs. 

         

        The ACMP result can be read by software or optionally sent out to a physical pin.  By routing the digital output to a physical pin, the ACMP can be used as a discrete circuit element that can be utilized by external electronic circuitry, including external feedback, with no firmware required beyond the initial configuration.

         

        You could think of the ACMP as a slower and lower-resolution ADC.  Since the ACMP has an internal reference voltage that is configurable by software to 64 divisions of VCC (or 1.25v, or 2.5V internal references), you can sweep through those programmable reference levels to compare to the input at each reference value and thereby slowly determine the analog value of the input pin.

        The ACMP has eight available pins in its input mux that can be used for either the positive or negative input, each of which must be used one at a time. 

         

        To demonstrate the operation of the ACMP, we will use the ACMP as a discrete level shifter.  One of the things that the ACMP can do is shift the voltage of a signal from a low-voltage device so that you can interface it to a higher-voltage device.  We will once again use the light sensor on the starter kit to detect potentially faint pulses from a blinking LED and convert those pulses into full-swing 3.3V signals.

        6_acmp_light_snese.png
        To generate light pulses, we will configure a simple GPIO on PD1 to blink at a rate based on the TIMER0 CNT register, and then wire an LED to the PD1 pin with a 300-ohm resistor in series.  The necessary code to set up an LED blink rate is almost exactly that of the code to trigger the ADC in the previous section, except we simply configure the TIMER0 to trigger 10 times a second instead of once per second.  Then, inside the interrupt handler, we clear the interrupt and toggle the PD1 pin.  

         

              CMU_ClockEnable(cmuClock_TIMER0, true);
              CMU_ClockEnable(cmuClock_GPIO, true);
         
              // Create a timerInit object, based on the API default
              TIMER_Init_TypeDef timerInit = TIMER_INIT_DEFAULT;
              timerInit.prescale = timerPrescale1024;
         
              TIMER_IntEnable(TIMER0, TIMER_IF_OF);
         
              // Enable TIMER0 interrupt vector in NVIC
              NVIC_EnableIRQ(TIMER0_IRQn);
         
              // Set TIMER Top value
              TIMER_TopSet(TIMER0, ONE_SECOND_TIMER_COUNT / 10);
         
              TIMER_Init(TIMER0, &timerInit);
         
              // Wait for the timer to get going
              while (TIMER0->CNT == 0)
                    ;
              // Excite the light sensor on PD6
              GPIO_PinModeSet(gpioPortD, 6, gpioModePushPull, 1);
         
              // Set up a GPIO output pin to push pull to the blink LED
              GPIO_PinModeSet(gpioPortD, 1, gpioModePushPull, 0);
         
        void TIMER0_IRQHandler(void)
        {
              TIMER_IntClear(TIMER0, TIMER_IF_OF);
              GPIO_PinOutToggle(gpioPortD, 1);
        }

         

        The voltage level detected by the light sensor from this blinking LED can be seen on an oscilloscope.

        6_small_swing.png

        The voltage swing generated by the light sensor (with the LED at about two inches from the light sensor) is between 500mV and 1V.  If we were to try to decipher the pulses from the light sensor with an ordinary GPIO pin in input mode, the voltage would never cross the thresholds needed to indicate a digital low/high transition.  Therefore, we will use the ACMP to “level shift” the light sensor output into full-swing 3.3V pulses on pin PE2.

        6_acmp_out.png
        The following code will configure the ACMP to act as a discrete analog comparator, and will output the result on pin PE2. 

         

              CMU_ClockEnable(cmuClock_GPIO, true);
              CMU_ClockEnable(cmuClock_ACMP0, true);
         
              ACMP_Init_TypeDef acmp_init = ACMP_INIT_DEFAULT;
              acmp_init.fullBias = true;
              acmp_init.vddLevel = 10;
         
              /* Init and set ACMP0 channel on PC6 */
              ACMP_Init(ACMP0, &acmp_init);
              ACMP_ChannelSet(ACMP0, acmpChannelVDD, acmpChannel6);
         
              /* Set up GPIO as output on location 1, which is PE2, without inverting */
              ACMP_GPIOSetup(ACMP0, 1, true, false);
              GPIO_PinModeSet(gpioPortE, 2, gpioModePushPull, 0);
         
              /* Wait for warmup */
              while (!(ACMP0->STATUS & ACMP_STATUS_ACMPACT));

         
        The ACMP negative input reference required to sample the light sensor waveform is around 600mV (at least, that is what the ambient lighting conditions in the room required when I measured the above scope shot).  This can be achieved by setting the VDD reference type and using 11 divisions of 64 using the formula 11/64 * 3.3V = 567mV.  It is important to remember that this is an ambient light sensor, and that the baseline of this waveform will move as the amount of ambient light in the room changes.  Therefore, the value of 11 divisions of VDD could change depending on the ambient lighting conditions.  For example, a bright room might require a value of 24 for the VDD reference scaling factor.  It would be best to stop the LED blinking, sample the room ambient voltage, and then set the VDD reference just above the room ambient voltage value.

        6_acmp_varied_sensor_distance.png

        In the oscilloscope waveforms, the original waveform from the light sensor is shown on channel 1, and the output of the ACMP is shown on channel 2.  The ACMP output waveform remains a steady 3.3V rail-to-rail voltage even as the distance between the LED and the light sensor varies over time, as shown in the waveform on the right.

         

        Note that once the analog comparator is set up as a discrete component, it will continue to function throughout interrupts, debug halts, and in energy modes EM0 through EM3. 

         

        It is also possible to sample the output of the comparator in software on ACMP0 Channel 6.  It can therefore be used to operate as a rudimentary ADC for quiescent signals by sweeping the negative input though all possible 64 steps to find which step triggers the output to go high.

         

        More details about the ACMP can be found in Application Note AN0020 Analog Comparator.

        The Voltage Comparator (VCMP) is a simplified instance of the ACMP that is tied directly to the VDD pin of the EFM32 MCU.  This gives your firmware the ability to detect the value of the supply voltage without using a GPIO pin or a channel of the ACMP.  More details about the VCMP peripheral can be found in Application Note AN0018 Supply Voltage Monitoring.  The application note also explains how to prepare for and react to dying battery situations.

         

        In the next section, we will use the TIMER and PCNT peripherals to count the input events, such as the pulses generated by a quadrature decoder or LED pulses.

      • Chapter 6 Addendum: Input Modes Explained - Part 1

        lynchtron | 04/115/2017 | 06: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.

         

      • Detecting User Input with Capacitive Touch and Passive Infrared (PIR) - Part 3

        lynchtron | 04/101/2017 | 12:21 AM

        15_title.png

         

         

        In part 2, we configured the host computer with Python and pyqtgraph.  You should now be able to do some great things with those tools.  We are now ready to build a Python script that fetches data from the serial port and displays the data to a graphing window.  You can see this full file Scrolling.py in Github.

         

        The first thing that we do in the script is import the libraries that we need to do most of the work for us:

        import pyqtgraph as pg 
        from pyqtgraph.Qt import QtCore, QtGui 
        import serial 

        Next, the script opens the serial port.  Note that on a Windows computer, the serial port that pySerial expects is the COM port minus one.  In my case, it uses COM3, so I pass in “3 -  1” to pySerial.  Edit this first line as necessary to match the port on your computer.

        # Set up serial port attached to Wonder Gecko 
        # Your computer may instantiate port numbers differently. 
        port = 3  - 1   # -1 is for Windows 
        baudrate = 115200 
        serial_port = serial.Serial(port, baudrate=baudrate, timeout=0) # 0 for timeoutNonblocking 
        serial_port.flushInput() 

        Then, we will set some configuration options and create a pyqtgraph plot, curve, and curve pen.

        # Switch to using white background and black foreground 
        pg.setConfigOption('background', 'w') 
        pg.setConfigOption('foreground', 'k') 
        
        # Create a pyqtgraph window object 
        win = pg.GraphicsWindow() 
        win.setWindowTitle('Capcitive sense plot') 
        
        # Create a plot inside the window 
        plot = win.addPlot() 
        
        # Create a list of 300 zeros (initially) to hold the streaming data 
        data = [0 for x in range(300)] 
        
        # Create a "pen" that is width of 1 and blue color 
        curve_pen = pg.mkPen(width=1, color='b') 
        
        # Creata "curve" that uses the curve_pen, with data points (symbols) that are filled and outlined with blue 
        curve = plot.plot(data, pen=curve_pen, symbolBrush='b', symbolPen='b', symbolSize=5) 
        
        # Createa place to store the incoming serial port characters 
        value_string = "" 

        We now need an “update” function.  This is the function that will be called from the Qt library to update the active window.  This function will read data from the serial port and then shift the data structure to the left in order to scroll the graph on the screen.

         

        # Define a function that will be called by pyqtgraph periodically to update the screen 
        def update(): 
            # Tell Python that these variables are to be tied to the outside global scope 
            global data, curve, value_string 
             
            # Only do something when the serial port has data 
            while serial_port.inWaiting(): 
                 
                # Read a single character 
                sample = serial_port.read(1) 
                 
                # Add this sample to the value_string if it is not whitespace 
                if sample.strip(): 
                    value_string += sample 
                else: 
                    # Process the data when we find a space in the read data, if the string is not empty 
                    if len(value_string) > 0: 
                         
                        # Convert the ASCII base 16 string to an integer 
                        value = int(value_string, 16) 
                         
                        # Shift the whole datalist by one sample to the left 
                        data[:-1] = data[1:] 
                         
                        # Add the new element to the end of the list 
                        data[-1] = value 
                         
                        # Set the curve with the data 
                        curve.setData(data) 
                         
                        # Clear the string for the next value 
                        value_string = "" 
            return 

         

         

        Next, we set up a timer that pyqtgraph uses to update the graph window.  We pass in a reference to the update() function that we just defined by specifying “update” without the parenthesis , and pyqtgraph will call on this update function whenever the timer expires, which is set here with a 50ms timeout.

         

        # Start Qt timer and pass the update function to it to set up periodic updates 
        timer = pg.QtCore.QTimer() 
        timer.timeout.connect(update) 
        timer.start(50)

         

         

        So far, we have objects ready to handle graphing, but we still don’t have any active windows.  The following lines will configure the pyqtgraph’s Qt system, which handles windowing.  Qt is a software library available for many languages that helps developers create graphical user interfaces.

         

        ## Start Qt event loop unless running in interactive mode or using pyside. 
        if __name__ == '__main__': 
            import sys 
            if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): 
                QtGui.QApplication.instance().exec_()

         

         

        And that’s it.  A file of under 100 lines is all that is required to fetch data from a serial port and create a live streaming graph in Windows, Mac, and Linux.  Note that most of this code was pulled from the pyqtgraph demo programs.  All I had to do was modify the formatting and add the pySerial stuff to the top of the file and in the update() function. 

         

        When you run this file on an OS command prompt by changing directory to the directory that contains the file (i.e. cd c:\users\david\documents” or equivalent), and then typing “python Scrolling.py”, you will see a graphing window that you can use to fine tune the capacitive sensor.  The graph starts out at 0, then quickly jumps up to the ambient count detected by the ACMP in the specified period on TIMER1.  Each touch event causes the count to drop, depending on how close your finger is to the center of the touch pad.

        capacitive_sense_graph_noise.png

         

        You can “zoom in” on the noise band by simply not touching the touch pad for 300 samples, at which time, pyqtgraph will automatically rescale the graph on the noise band as shown in the figure below.  This shows that the noise band is only two counts wide.

        capacitive_sense_ambient.png

         

        Now that you have a tool to create a graph of the ongoing live count, you can use this to fine tune the capacitive sense circuit.  You can adjust the frequency of the oscillator, the frequency of TIMER0 and TIMER1, or change the size and structure of the touch pad, and then use the graph to ensure that your touch event is still 5x the noise band.  You can use copper tape to adjust the size of your capacitive sense pads during the prototyping stage.

         

        Passive Infrared (PIR) Sensor Board Summary

        The PIR sensor board that is obtained from Adafruit contains a raw pyroelectric sensor, some discrete electronics, a controller chip to make sense of the pyroelectric sensor analog output, and a Fresnel lens, which creates an omnidirectional detection field for the sensor.  It also contains some trimming potentiometers to allow you to adjust the delay time, sensitivity of the sensor, and the width of the pulse on its output pin when it detects movement.  There is a great tutorial over at Adafruit to learn more about the sensor and the board: https://learn.adafruit.com/pir-passive-infrared-proximity-motion-sensor/.

         

        pir_sensor.png

        We will be using the PIR sensor board to simply produce a signal whenever someone approaches our circuit, or doorbell in our example, so that we wake up the EFM32, illuminate an LED, and start sampling the capacitive sense button.  It makes no sense to keep the EFM32 awake scanning for capacitive touch inputs and an LED illuminated continuously if there is no one standing in front of our sensor.

         

        Attach the PIR Sensor and Illuminate an LED

        The PIR sensor board has just three pins and is powered by 3 to 5V, so the connection to the Starter Kit is simple.  Attach the sensor to the Starter Kit per the following table.  Note that the Adafruit board contains a 3.3V regulator, so even though we are supplying 5V to the board, it is only producing 3.3V on the OUT pin, and it is therefore safe to use on the Starter Kit.

         

        Starter Kit           PIR Sensor Board
        5V                      +5V
        PC9                    OUT
        GND                   GND

         

        Note that PC9 is only available on the row of pins called J100 on the Starter Kit.  This pin is the only pinreadily available on the Starter Kit capable of bringing the MCU out of EM4 deep sleep state.

         

        wonder_gecko_em4_pin.png

        wonder_gecko_em4_pin._photo.png

        The jumper on the corner of the PIR board is used to set the “retriggering” option.  In our case, it should be set closest to the board edge, which means that the OUT pin will remain high for as long as there is motion detected in the sensor range. 

         

        Configure PC9 as an input and enable the PE2 as an output, which is connected to one of the LEDs on the Starter Kit, using the following code inside the setup_capsense() function:

         

              // Set the LED as push/pull
              GPIO_PinModeSet(gpioPortE, 2, gpioModePushPull, 0);
         
              // Install the PIR sensor on PC9
              GPIO_PinModeSet(gpioPortC, 9, gpioModeInput, 0);

        Add the following code to the top of your while loop to illuminate the LED whenever the PIR sensor’s OUT pin is high. 

         

                    if (GPIO_PinInGet(gpioPortC, 9))
                    {
                          GPIO_PinOutSet(gpioPortE, 2);
                    }
                    else
                    {
                          GPIO_PinOutClear(gpioPortE, 2);
                    }

        Note that the sensor holds the OUT pin high for a few seconds even on the lowest DELAY setting as set by the potentiometer.  This is perfect for our use, since we would want to illuminate the LED for our doorbell for a few seconds after movement has ceased.  You should now see the PIR sensor illuminate the LED0 on the Starter Kit whenever the PIR detects motion.  Note that there is a few seconds of holdoff after the LED has turned off before the PIR sensor board will arm itself again.

         

        Use the PIR Sensor to Wake the EFM32 from EM4 Sleep
        The PC9 pin that we used for the PIR board OUT pin was chosen because it is one of the possible choices as shown in the Wonder Gecko Reference Manual as being able to wake up the MCU from EM4.  Once configured for EM4 wake up, these pins will remain active as inputs even though all other GPIO’s are disabled.

         

        gpio_pins.png

         

        To configure PC9 as an EM4 wake up pin, use the following setup_em4() function:

         

        #define EM4_WAKEUP_ENABLE_PC9     0x04
        void enter_em4(void)
        {
              EMU_EM4Init_TypeDef em4_init = EMU_EM4INIT_DEFAULT;
              EMU_EM4Init(&em4_init);
         
              // Set the wake up enable on PC9 and the polarity of high from the PIR OUT
              GPIO->EM4WUEN = EM4_WAKEUP_ENABLE_PC9;
              GPIO->EM4WUPOL = 1;
         
              // Set EM4WUCLR = 1 in GPIO CMD, to clear all previous events
              GPIO->CMD = 1;
         
              EMU_EnterEM4();
         
              // Now, the system can only be awakened by PC9 or reset, and it acts as
              //   if it is coming out of reset.  Code beyond this point will
              //   never execute.
        }

        Now, augmenting our main while loop is simple.  After we clear the GPIO pin PE2 to LED0, we call the enter_em4() function.  Keep in mind that once this function is called, any data in RAM is lost.  The board can only exit EM4 energy mode by the PC9 pin going high, through the reset signal, or through a power cycle.  In all of those cases, the board will be starting up from the Chip_Init() statement.  The following is the final main() function:

         

        int main(void)
        {
              /* Chip errata */
              CHIP_Init();
         
              setup_utilities();
         
              setup_capsense();
         
              while (1)
              {
                    // Check the PIR OUT signal
                    if (GPIO_PinInGet(gpioPortC, 9))
                    {
                          // PIR is high, so set the LED0 to ON
                          GPIO_PinOutSet(gpioPortE, 2);
                    }
                    else
                    {
                          // PIR is low, so turn off LED0 and enter EM4 engergy mode
                          GPIO_PinOutClear(gpioPortE, 2);
                          enter_em4();
                    }
         
                    // Clear the count and start the timers
                    measurement_complete = false;
                    TIMER0->CNT = 0;
                    TIMER1->CNT = 0;
                    TIMER0->CMD = TIMER_CMD_START;
                    TIMER1->CMD = TIMER_CMD_START;
         
                    // Now, wait for TIMER0 interrupt to set the complete flag
                    while(!measurement_complete)
                    {
                          EMU_EnterEM1();
                    }
         
                    // Now observe the count, send it out the USART
                    print_count();
         
                    // Delay to not overwhelm the serial port
                    delay(100);
              }
        }

        When you run this code in Simplicity Studio, you will see that when the LED0 is not lit, the debugger loses connection with the MCU.  This is expected, because the debug interface shuts down in EM4 energy mode.  When you wave your hand over the PIR sensor, you should see the LED0 illuminate again.  If you watch the Python graphing window (or a terminal output) connected to the USART, you will see that no capacitance counts are streamed while the LED0 is off.  Once you wake up the MCU with the PIR OUT pin, the count of capacitance values resumes on the USART.

         

        When working with EM4 mode, you can sometimes get “stuck” in EM4 if the MCU is booted and then goes to EM4 state too quckly.  You will have to go through the trouble of erasing flash and recovering the part outside of the normal programming process.  To keep this from happening, you should create a “GPIO trap” which looks for a pushbutton pin to be pressed at startup and if so, it does not enter EM4.  Then, by holding the pushbutton while you attempt to reprogram the part, it will prevent it from reentering EM4 too quickly.

         

        This concludes the chapter on capacitive and PIR sensing.  You should be gaining the skills necessary to integrate several of EFM32’s peripherals in a single project to make something awesome.

         

      • Detecting User Input with Capacitive Touch and Passive Infrared (PIR) - Part 2

        lynchtron | 03/89/2017 | 09:37 AM

        15_title.png

         

        In part 1, we configured the capacitive sensor interface to sample touches on the Wonder Gecko touch slider.  In this section, we will get our computer ready to run Python and the freely available pyqtgraph libary, which is a cross-platform graphing tool that is easy to setup and use.

         

        Graphing the Capacitive Touch Count


        This signal to noise ratio requirement requires a way to graph the count value to observe it over time.  The easiest way to do that is to feed the data from the EFM32 to a host computer, where we can run a graphing program in real time. 

         

        capacitive_sense_graph.png

        Per AN0040, we need to be able to characterize the number of pulses on a touch pad with no touch (i.e. the ambient value) and when a user is touching the pad.  The count when not touched should be 5x the count when touched to have a reliable system.

         

        In the first example, we configured the SysTick for 1ms interrupts, then counted 100 ticks, or 100ms of time before reading the CNT register in TIMER0.  This is an easy way to get started, but is not efficient.  The SysTick interrupt brings the system out of EM1 every millisecond to add one to the counter.  Rather than rely on the SysTick interrupt and a busy while loop, we will rewrite the application to make use of TIMER1 to keep track of the sample time.  This will allow the MCU to stay in EM1 sleep state until the TIMER1 count is complete, and then fetch the TIMER0 CNT value inside the TIMER0 interrupt.

         

        Set up the TIMER1 in the setup_capsense() function:

          

              // Set up TIMER0 for sampling TIMER1
              CMU_ClockEnable(cmuClock_TIMER0, true);
         
              /* Initialize TIMER0 - Prescaler 2^9, top value 10, interrupt on overflow */
              TIMER0->CTRL = TIMER_CTRL_PRESC_DIV512;
              TIMER0->TOP  = 10;
              TIMER0->IEN  = TIMER_IEN_OF;
              TIMER0->CNT  = 0;
         
              /* Enable TIMER0 interrupt */
              NVIC_EnableIRQ(TIMER0_IRQn);

         

        The TOP value is set to 10 in this code with a prescaler set to 512.  Once again, our sample window can be any value we like.  If we set TOP to a small value or use a small prescaler, we will get a very responsive interface at the cost of higher energy consumption.  If we set the TOP value to a large value or use a large prescaler, the TIMER1 interrupt will occur less often, saving energy but creating a less responsive interface.  It is up to you to find values that work best for your application.

         

        Now, to fetch the count, we can make the count variable global, as well as a measurement_complete flag, which is set to true in the TIMER1 interrupt handler.  The resulting main code and TIMER1 interrupt handler is shown here:

         

         

        // Global variables
        volatile unsigned int count = 0;
        volatile bool measurement_complete = false;
        
        int main(void)
        {
        	/* Chip errata */
        	CHIP_Init();
        
        	setup_utilities();
        
        	setup_capsense();
        
        	while (1)
        	{
        		// Clear the count and start the timers
        		measurement_complete = false;
        		TIMER0->CNT = 0;
        		TIMER1->CNT = 0;
        		TIMER0->CMD = TIMER_CMD_START;
        		TIMER1->CMD = TIMER_CMD_START;
        
        		// Now, wait for TIMER0 interrupt to set the complete flag
        		while(!measurement_complete)
        		{
        			EMU_EnterEM1();
        		}
        
        		// Now observe the count, send it out the USART
        		print_count();
        
        		// Delay to not overwhelm the serial port
        		delay(100);
        	}
        }
        
        void TIMER0_IRQHandler(void)
        {
        	// Stop timers
        	TIMER0->CMD = TIMER_CMD_STOP;
        	TIMER1->CMD = TIMER_CMD_STOP;
        
        	// Clear interrupt flag
        	TIMER0->IFC = TIMER_IFC_OF;
        
        	// Read out value of TIMER1
        	count = TIMER1->CNT;
        
        	measurement_complete = true;
        }

         

        All that is left to do is to define the print_count() function that we will use in lieu of a hardware breakpoint that we used in our earlier experiments.

         

        We normally would use the SWO output from the Starter Kit to the host computer for simple debug print messages, but it is difficult to reroute those messages to another program for further analysis.  Therefore, we will use the USART and a USB-to-UART adapter to route the messages to a serial port on the host computer

         

        Recall that we used the Silicon Labs USB-to-UART CP2104 MINIEK board in chapter 8.  We can reuse that board here and connect it per the following table:

         

        Starter Kit                 CP2104 MINIEK
        PC0 - USART0          TX RXD
        PC1 - USART0          RX TXD
        GND                          GND

         

        cp2104.png

        Add the following to your setup_capsense() function to enable USART0:

          

              // Set up USART0 for graphing via PC serial port on pins
              CMU_ClockEnable(cmuClock_USART0, true);
              USART_InitAsync_TypeDef usart_settings = USART_INITASYNC_DEFAULT;
              USART_InitAsync(USART0, &usart_settings);
         
              // Enable TX only at location 5
              USART0->ROUTE = (USART0->ROUTE & ~_USART_ROUTE_LOCATION_MASK) | USART_ROUTE_LOCATION_LOC5;
              USART0->ROUTE |= USART_ROUTE_TXPEN;
         
              // Set the pin as push/pull in the GPIO
              GPIO_PinModeSet(gpioPortC, 0, gpioModePushPull, 0);

         

         Then, the following function is defined to send the count value out through USART0:

          

        // Prints out the global count variable to the USART
        void print_count()
        {
              // Create a place to store a message
              char message[6];
         
              // Format the string as count, encoded in hex, with a space
              sprintf(message, "%x ", count);
         
              // A string pointer to the start of the message
              char * string = message;
         
              // While the data pointed to by the string is not null
              while (*string != 0)
              {
                    // Send the dereferenced value of the pointer to the USART
                    // And then the pointer is incremented to the next address with ++
                    USART_Tx(USART0, *string++);
              }
        }

        Open a terminal emulator program as we did in chapter 8.  You can use Putty or TeraTerm, or the screen utility on Mac and Linux.  Set the baud rate to 115200.  Make sure that you select the serial port that is assigned to the MINIEK board and open it in the terminal emulator.  If all goes well, you will see a list of count values that scrolls forever.  Touch the left-most touch pad on the Starter Kit, and you will see the values change.

          

        4a 48 48 4a 48 48 48 48 48 48 4a 48 48 4a 48 48 4a 48 
        48 4a 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48
        4a 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48
        48 48 48 4a 48 48 4a 4a 48 48 48 48 48 4a 48 48 4a 48
        48 48 4a 48 48 48 44 36 2c 2c 2c 2c 2c 2c 2a 2a 2c 2c
        2c 2a 2c 2c 2c 2c 2c 2a 2c

         

        The touch event can be seen at the end of this listing.

         

        Configuring Your Computer to Run Python and pyqtgraph

        We can graph the data arriving at the host computer’s serial port in real time with the help of a Python script and Python libraries called pySerial and pyqtgraph.  

         

        The Python scripting language has many benefits over the C language that we have been using thus far in programming the EFM32.  The first thing that you need to know is that Python is dynamically typed.  What this means is that you don’t need to declare a variable as a specific type, like uint32_t.  Any variable can hold any data and can be changed at runtime; Python automatically manages this for you, as it also manages memory.  Also, Python is object oriented, which allows for each object to keep track of its own state, allowing more powerful and reusable code.

         

        Python is interpretive, which means that we don’t compile and link during the build process.  There is no resulting executable or binary file.  Every script file is evaluated line-by-line as it runs, and you can even invoke Python by itself as an “interpreter” which allows you to write code and explore functions and return values in a live and active session.  There are plenty of Python IDE’s with breakpoint and single-stepping functionality, but they are not strictly required, as the interpreter provides some of those benefits. 

        Python and the libraries are cross-platform, meaning that they will work on Windows, Mac, and Linux computers.

         

        IMPORTANT NOTE: Python uses tabs and/or whitespace to indicate an indention block.  You don’t need to use curly braces {} after an if statement.  This is a great feature that saves keystrokes, but can be maddening if you use an editor that mixes tabs and spaces.  Therefore, make sure to use a good editor or IDE when editing Python scripts that converts tabs into spaces.  For example, Notepad++, Komodo Edit, PyCharm, or Pydev, the last of which uses Eclipse.

         

        If you have a Mac or Linux computer, Python is likely already installed.  If not, just visit http://python.org and follow the instructions to install it. 

         

        If you have a Windows computer, download the Python2.7 “Windows x86 MSI installer” from https://www.python.org/downloads/.  At the time of this writing, the sub version was 2.7.12, but any newer 2.7.x versions should work fine.  This code has not been tested on the newer Python 3.x branches.

         

        IMPORTANT:  For Windows users, make sure that you modify the default setting “Add python.exe to Path” to “Will be installed on local hard drive” as shown in the figure below.  This is required to run the commands listed in this chapter. 

        python_install.png

        Once Python is installed, open a command window (or console) on your computer and type “python --version” at the prompt.  You should see a message that displays the python version.  If this doesn’t work for you, resolve this problem before continuing.

          

        C:\WINDOWS\system32>python --version
        Python 2.7.12

         

        Next, install the libraries needed for the graphing script.  Type the following commands at your OS command prompt:

         

        python -m pip install --upgrade pip
        pip install pyserial
        pip install -U PySide
        pip install pyqtgraph
         

        Then, at the OS command prompt, type python:

        C:\WINDOWS\system32>python
        Python 2.7.12 (v2.7.12:d33e0cf91556, Jun 27 2016, 15:19:22) [MSC v.1500 32 bit (Intel)] on win32
        Type "help", "copyright", "credits" or "license" for more information.
        >>> 

         

        The three greater-than symbols is the Python interpreter prompt.  Type the following commands to ensure that all libraries are installed:

         

        >>> import pyserial
        >>> import pyqtgraph.examples
        >>> pyqtgraph.examples.run()

         

        When you run these commands, you should not see and error messages and a window should open on your computer that looks like the following.  If so, it confirms that you are ready to run the graphing code.  This is a demo application for pyqtgraph and demonstrates all the neat things you can do with it.

        pyqtgraph_window.png

        In the next section, we will use our serial data port and Python GUI tool to create a program that graphs the output from the capacitive sensor in real time.

      • Detecting User Input with Capacitive Touch and Passive Infrared (PIR) - Part 1

        lynchtron | 03/79/2017 | 03:21 PM

        15_title.png

         

        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. 

         

        pir_sensor2.jpg

        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.

         

        capacitive_sense_pad.png

        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.

        15_touch_slider_schematic.png

        capacitive_sense_buttons.png

         

        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);
              ACMP1->CTRL = ACMP1->CTRL | ACMP_CTRL_IRISE_ENABLED;
         
              // 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
              PRS_SourceSignalSet(0, PRS_CH_CTRL_SOURCESEL_ACMP1, PRS_CH_CTRL_SIGSEL_ACMP1OUT, prsEdgePos);
         
        }

        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
              setup_utilities();
         
              setup_capsense();
         
              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))
                    {
                          EMU_EnterEM1();
                    }
         
                    // 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.

         

        capacitive_sense_raw_frequency.png

        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.

      • Essential Best Practices and Debugging Tips for EFM32 Project Success - Part 6

        lynchtron | 03/69/2017 | 01:49 PM

        debug_teaser.png

        In this final part 6 of the Best Practices and Debug Tips chapter, you will learn more about probably the most valuable skill in your embedded toolbox...debugging your live code in hardware.

         

        Debug Issues Like a Genius
        The Simplicity Studio IDE’s most powerful feature is the ability to debug live programs running on your EFM32 device over a JTAG connection. With this tool, you are not limited to simply programming your embedded device and then hoping it works. Instead, you have introspection tools to interrogate the software as it runs as well as artificially alter the value of variables and memory for test coverage or to find hard-to-find bugs.

         

        1. Build the project before running the debugger
        Whenever you begin debugging your code, build the code before you go straight to debugging it. The debugger icon in the toolbar will do both steps at once and if your build has errors, the debugger will get launched after the failed build, which then results in confusing errors due to loading a project into the debugger that doesn’t exist.


        2. Turn on all debugging features
        Make sure that you are using a “Debug” build configuration by selecting the appropriate project from the drop down menu next to the debug icon in the toolbar.

         

        debug_as.png

        Also ensure that your debug build has no optimizations (the default) and that the debug level is set to -g2 or -g3, as found in the Project > Properties > C/C++ Build Settings menu.


        In order to get the maximum benefit out of your debug session, you will want to make sure that the debugger stops and breaks in whenever anything “bad” or unusual happens. Under the dropdown next to the debug button, click on Debug Configurations. In the Debug Configurations window, in the Exceptions tab, click on all of the types of exceptions that you would like the debugger to catch. These are not enabled by default.

         debug_options.png

         

        3. Unlock debug access when the debugger refuses to load
        Sometimes when you develop code for embedded applications, you do some bad things that can lock up your MCU early in the boot sequence. There is no OS or other supervisor in the system to rescue your program. When this happens, the normal flash routine over JTAG fails, and the debugger informs you that your debugger cannot start. You should do all of the obvious things like disconnecting the hardware from your computer and perhaps restarting your computer or the Simplicity Studio IDE, but if that doesn’t fix it, you can try unlocking debug access to your MCU.


        The Unlock Debug Access option in the Flash Programmer tile will erase the contents of the flash memory on your MCU and leave the MCU in a state that allows it to be flashed with programming again. Just beware that if you flash it again with bad programming that does something bad early in the boot sequence, you will need to unlock debug access again.


        Just remember that you have to make sure that your Detected Hardware is set to Detect Target Part in the main Simplicity Studio home screen, and that your Debug Mode is set to MCU or Out in the Kit Manager in order for the Flash Programmer to display the proper options. See the Connect to your own PCB over JTAG as if it were a Starter Kit section in this chapter for more information on how to connect to your hardware, whether it is a Starter Kit or your own hardware board.

        flash_programmer.png

         

        4. Use a blank project to fix debugger launch issues
        Whenever you are starting a new project, build a completely empty_project.c first. Then, launch the debugger on that empty file to prove that all of the connections between the host computer and EFM32 hardware platform are working. Sometimes, you will get mismatches between the type of EFM32 part detected versus the type of EFM32 part that is specified in your project. Solve that problem before you try to add your own code and library files to the mix.


        Once you have your code up and running, sometimes things just break and your code refuses to load into your hardware. After you have tried to unlock debug access and other hardware tricks in the previous steps, your problem might lie in the project settings. Something could have changed in your environment. Load an empty project and see if you can get it to work again, without any of your own code. If it works, then the problem is buried somewhere in your project settings. You can choose to hunt down the problem in your existing project or create a new project and copy all of your source files into the new project, and then modify the Project Properties settings of the new project to match that of the old project. This process should get you back in business.


        5. Write code to help the debugger
        When developing code to be used during a debugging session, there are some things that you can do to help make the process easier:

        • Define arrays that you want to examine in watch windows with constants. If the debugger can tell how big an array is at compile time, it can offer you the appropriate number of elements to be able to inspect them. Otherwise, you will have to manually add a range of indices for the debugger watch window every time you launch the debugger.
        • Use a volatile variable to control execution of a block of code that you don’t want the MCU to automatically execute every time. If you are debugging some code that erases a big chunk of flash memory or some other dangerous code right at startup, it will do it every time the MCU boots. You can prevent it from doing that by encapsulating the dangerous code inside block of code that will only execute if you use the debugger features to make it happen. For example:
          volatile int i=0;
          if (i)
          {
          // Do something dangerous, which could reset the system, etc.
          }

          Put a breakpoint on the if statement, then “Move to Line” in the debugger to move to the first line inside the if statement that you want to execute. Then on subsequent resets, the code inside the if statement will never execute. The volatile declaration of i prevents the compiler from optimizing away the block of code from within the if statement.
        • Don’t use identical variable names at multiple scopes within a module. The debugger can get confused and show you the wrong value when you hover your mouse on a variable in a live debugging session. If you have a global variable named foo and a local variable named foo, the debugger doesn’t always show you the correct value of the foo variable based on the scope. Keep variable names unique and the debugger will give you the proper value of your inspected variables.
        • Don’t set breakpoints or play around too much in the IDE while your project is running live and Simplicity Studio is looking for a breakpoint. This can alter the timing of your code and cause it to miss interrupts. Keep your hands off the IDE as much as possible until a breakpoint is reached.
        • If you have trouble setting interrupts on a line of code, that means that either you have too many breakpoints already set in the current project, or the compiler thinks that the code that you are trying to break on is unreachable. For example, an if (0) statement will never execute, and the compiler knows this. Disable all other breakpoints, restart the IDE if necessary, clean the project and rebuild. Then, try setting breakpoints again.

        6. Attach to running instances without resetting the MCU
        If you are running a project on your own hardware that was loaded with a Debug configuration, you can attach to it and inspect it without resetting the device. This is particularly helpful if your system has mysteriously locked up and you want to get in there to inspect things without starting a new debugging session. Note that sometimes the act of connecting the JTAG cables from the Starter Kit to your custom project can cause a reset. You may be able to mitigate this issue by grounding your custom project to your Starter Kit first with a jumper cable, and then connecting the JTAG cable, or just leave the JTAG cable attached throughout the test process.


        In order to attach to a running project, select Run > Attach to menu option and then select the correct project. Note that the version of software running on your target hardware and the project selected from the Attach to menu must be the same version or you will get nonsensical results. Once code compiles and the debugger attaches, you may have to double-click on the entry in the top left window, called Silicon Labs ARM MCU: etc., expand the dropdown icon next to it, and then find the <project>.axf file. Once you find that and highlight it, you can press the pause button or set breakpoints in your code as if you had launched it from the debug tool.

        debug_scope.png


        7. Force activity on a configured peripheral
        When you are configuring a peripheral for the first time, it can be a struggle to get to that first glimmer of activity that confirms that you have the right configuration of GPIO pins, route, peripheral and that you have properly interpreted all of the necessary instructions to get everything going. In summary, here are the steps necessary to enable any peripheral in an EFM32 device:
        1) Enable the GPIO clock
        2) Enable the peripheral clock (i.e USART, I2C, DAC, etc.) and other necessary clock sources
        3) Configure and enable the peripheral
        4) Route the pins used by the peripheral through to the GPIO
        5) Configure the pins used by the peripheral in the GPIO (i.e. push pull, input, etc.)


        To be sure that the GPIO pins for your chosen peripheral are connected to the appropriate place in your hardware as you expect, you can create an empty project and simply set or clear the GPIO. Verify that you see the change on an oscilloscope or multimeter. Once you are sure that you are connected to the correct GPIO pins, you can resume debug of the peripheral you are trying to program. If you are like me, you forgot to enable the peripheral clock.


        8. Don’t use print statements over UART to debug timing issues
        Debugging on an MCU is a tricky business because the limited resources means that any attempts to observe the system will impact the performance of the system. The timing changes caused by debug print statements can cause the issue that you are trying to find to disappear. You don’t have the luxury of multiple cores and deep memory resources to handle such debug printing in the background.


        You can mitigate the timing effects of debug print statements by utilizing a print buffer with an interrupt-based mechanism. If you were to simply place a character from a print statement onto the UART, then wait for the character to complete before placing another character on the UART, you will slow down execution of your single-threaded embedded application. By utilizing a ring buffer and interrupt to feed the UART, you will lessen the impact your embedded application.


        If a UART is not available for debug output or if the time spent servicing the UART print statements are still causing timing issues, you can place all of your debug print statements in a debug buffer and examine the contents of the buffer through the Simplicity Studio debugger. Simply examine the value of your print buffer in the debugger and it will automatically translate your buffer to ASCII text, allowing you to see whatever message was transferred into it before your error occurs.


        Finally, toggling different GPIO pins in various places in the code can help illustrate the flow of the program and if things are executing in the order you expect. You can connect those GPIOs to LEDs for events that happen slowly enough to observe or to an oscilloscope, which will provide precise timing information.


        Conclusion
        I hope that this guide has helped you gain a better understanding about how to set up, develop, and debug your project for success. Don’t get discouraged if you run into a lot of issues. Embedded development is never easy. It requires a serious investment of effort and time to get it right. There are opportunities everywhere to learn something new. Stick with it, and then gloat over your amazing accomplishment!

      • Essential Best Practices and Debugging Tips for EFM32 Project Success - Part 5

        lynchtron | 03/60/2017 | 09:19 PM

        debug_teaser.png

        Build Source Code Like a Pro

        You are lucky to be building your code in Simplicity Studio, because the installer sets everything up for you and you can get started with debugging your projects in minutes after the installer is finished.  But don’t overlook the build tools.  It wasn’t always this easy to get it all working and you may have lost appreciation for what all of these tools do.  There are a lot of things that are going on under the hood to take your human-readable C code and turn into a binary image that is then flashed to your MCU.

         

        1. Don’t forget to copy or link the emlib files
        When you create a new project from scratch and start to reference the emib libraries such as em_cmu.h or em_emu.h, you will need to copy or link the implementation files for those header files in your emlib directory in your new project. Simplicity Studio automatically links to the header files (the .h files) for all of the standard emlib libraries, but doesn’t link to the implementation files. Therefore, it is up to you to navigate to the Simplicity Studio installation folder that contains those library files and copy them into your emlib directory, or to use Simplicity Studio’s project Properties to link to those files.


        To copy or link implementation files have different benefits and tradeoffs. If you copy the implementation files to your project, your project will retain a private copy of the implementation of a library file that won’t change as the Simplicity Studio version is updated. However, the header file will change as the Simplicity Studio version is updated. This can create some mismatches between the declarations and implementations of the library files. If you link to an implementation files, your library files will change as the Simplicity Studio version is updated, and this could have adverse implications to your software. The solution that would give you the most control is to copy both header and implementation files into your project, and remove the link to the header files.


        Beware that if you forget to add a link or copy of an implementation file for a library, some functions that are implemented solely in the header files will still work, while functions that are implemented in the .c files will sometimes give non-obvious errors in the build output.


        2. Solve your issues from top to bottom
        Whenever you build your code, the compiler will send lots of information to the build output console window in Simplicity Studio. The window scrolls and if it finds errors, your build console will be sitting at the last error that it found. You should not debug that last error. Scroll up in the window and debug the very first problem that the compiler found. The warnings and/or errors that occur later in the build process could be due to that first error, and you will be chasing the wrong problems if you try to solve the later issues that were caused by the root issue. You can also make use of the Problems tab in the bottom of the Simplicity Studio build console.


        3. Don’t ignore warnings
        Compiler warnings are set to be ignored by default in the build process. If your code generates errors, the build will fail, but if it only generates warnings, the build will succeed and you can then flash your MCU with the program. You might not even notice the warnings scroll by on the build console window. You have to scroll back and look for any line that has a yellowish background. Make sure that you understand what the warning is telling you before you ignore it. Some warnings, like an unused variable warning, might be just a nuisance that you can safely ignore. However, if you intended to make use of a variable that was important for a calculation, the warning will help save you from a logically incorrect solution. If you resolve all of your warnings, you will not see any colored background scroll by as the build process commences.


        In order to monitor the output of functions during debugging, you will sometimes be left with unused variable warnings that are not an issue. If this is the case, you can use a C compiler attribute instruction to tell the compiler that the unused variable is intentional with the following code:

         

        uint32_t __attribute__((unused)) foo = some_function_with_return_value_to_inspect() ;


        Or, you can create a preprocessor directive to use throughout your code:

        #define UNUSED __attribute__((unused))
        void some_function()
        {
             int UNUSED foo = some_function_with_return_value_to_inspect();
        }


        There is another thing that you can do to remove unused parameters in functions. Sometimes, you don’t need to make use of all of the functions given to you in a pre-defined function, as in an interrupt handler or other system-defined callback function. By declaring the functions parameters as void in your customized implementation, you will tell the compiler that you don’t intend to use those parameters in your implementation.

         

        void some_function(int foo)
        {
        (void) foo;
        }


        Beware that if you happen to miss a warning that scrolls by somewhere in your build window, it will mark that file as built, and it won’t rebuild it again until there are changes to that module, so you won’t see the warning again until changes are made. This could result in a problem that seemingly pops up out of nowhere in a later build when unrelated code is modified. You can always force a rebuild of all files by cleaning your build environment from the ProjectClean... menu option and then rerunning the build tools.


        You can configure the compiler to treat warnings as errors if you are brave enough and committed enough to clean up all warnings. Set this option in:


        Project > Properties > C/C++ Build Settings >Warnings > Warnings as Errors (-Werror)


        4. Interpret the build output size report
        When you build your project, a bunch of text scrolls by on the build output console. When it is done and if the build succeeded, the last thing that it does before giving you the “Build Succeeded” message is to run the size tool:

         

        Running size tool
        arm-none-eabi-size "energy_profile_only.axf"
        text data bss dec hex filename
        1188 104 32 1324 52c energy_profile_only.axf


        This tool is giving you a report about how flash and RAM is required for the code that you just built. Here is the decoder of what these things mean:

         

        • text
          • The number of bytes in EFM32 flash memory reserved for programming instructions and constants. The interrupt vector table is also included in this memory.
        • data
          • The number of bytes in EFM32 flash memory reserved to be used by initialized data values. These are the values that get assigned to ordinary variables either at program startup or at function invocation.
        • bss
          • The number of bytes of RAM reserved when the program starts, which is the sum of all of the global variables required by the program, which end up on the heap.
        • dec
          • The sum of text + data + bss. This number has no real significance.

         

        If you try to build a file that has more RAM allocated than what is available in your particular EFM32 part, you won’t even get to the build report.  You will get a fairly straightforward compiler error.  For example:

        int really_big_array[10000];
        void some_function()
        {
                really_big_array[0] = 0;
        }
        int main(void)
        {
                /* Chip errata */
                CHIP_Init();
               
                some_function();
        }

         

        Results in:

        c:/...bin/ld.exe: some_program.axf section `.bss' will not fit in region `RAM'
        c:/...bin/ld.exe: region RAM overflowed with stack
        c:/...bin/ld.exe: region `RAM' overflowed by 7368 bytes

        Keep in mind that this is reporting the memory that is allocated when your program starts running.  The amount of memory in use once your program starts will change based on the functions that are called and the memory that gets allocated dynamically. 

         

        5. Make changes to the build process in project properties
        The project properties menu option has many settings that control your build. This tool is what is saving you the headache of command-line manipulation and stores all of the settings in the .project and .cproject files in your project’s directory on disk. The most important aspects for the build process are configured in the Project > Properties > C/C++ Build Settings menu. Here, the overall build can be selected, whether you are building to be able to debug a project or release it for production. Then, the Memory Layout, C Compiler, Assembler, and Linker tools can all be individually controlled, which gives you the same power and flexibility as if you had run each of these tools from the command line, one at a time.

         build_options.png

        A debug build allows you to single-step through execution and inspect the contents of variables and memory, but is not optimal for performance or code size. The top node on the tree of options is Debug Settings, which allows you to control the debug level.

         

        A release build is optimized for better performance and code size, and the level of optimization can be controlled from within this same Settings area in the GNU ARM C CompilerOptimizations menu. You can see that selecting Release versus Debug changes the level of optimization from Most (-O3) to None (-O0).


        The Memory Layout menu item in the Settings menu gives you the ability to place your program in flash and RAM at an address that you specify. You can use a custom linker script if your needs are complex, or simply click on the options to “Override default flash options” or “Override default RAM options.” By overriding the default flash option’s origin, you can write multiple programs into the same EFM32 chip without erasing the other. This is how bootloaders are configured.


        A bootloader typically starts at address zero and is limited in size (with the LENGTH parameter) so that the build tools will warn if your bootloader is getting too big to fit in the allocated space. Then, your application program (an entirely different project in Simplicity Studio) is configured to start at an address that is higher than the bootloader. This way, loading your application code from Simplicity Studio over JTAG doesn’t erase the bootloader. Note that in order for your application code to run at an address other than zero, the bootloader must properly load the program counter with the address of the application program so that your application can run from reset. See chapter 16 for more detailed information about boot loaders.


        6. Investigate build tool input and output further in build files
        When your build process finishes and you have a successful build, Simplicity Studio places the output files of the build process in one of two directories in your project. The directories are called GNU ARM v4.8.3 – Debug or GNA ARM v4.8.3 – Release, depending on if your build was a debug or release build.

         

        file_list.png

         

        These directories are the working folders for the build process and should not be modified directly—they will be overwritten on every build, and can therefore be excluded from revision control systems. These directories contain files that are used as inputs to some of the build tools and the outputs of some tools. You can, however, examine the results of the build process to see what the tools are doing. It is beyond the scope of this chapter to describe these files in detail, but the following table provides a summary.

         

        • makefile and *.mk files
          • Scripts used to configure the compiler, or “make” tool, which is auto-generated by Simplicity Studio on your behalf, based on the settings in the Project Properties settings.
        • <project>.ld
          • The linker script that Simplicity Studio generated on your behalf, based on the settings in the Project Properties settings. The linker runs after the compiler.
        • <project>.map
          • The output of the linker tool. This file contains information about where in memory the build tools have placed your functions and data.
        • <project>.axf
          • Object code plus debugger information. This file is used by the Simplicity Studio debugger to be able to debug your software live on the device
        • <project>.hex
          • An ASCII-text representation of machine code for your build
        • <project>.bin
          • Machine code binary image. This is the file that will ultimately be transferred into flash during programming and the source file that is used for bootloaders.

         

        In the next section, we will cover the final step, debugging your software on the EFM32.

         

      • Essential Best Practices and Debugging Tips for EFM32 Project Success - Part 4

        lynchtron | 02/46/2017 | 04:15 PM

        debug_teaser.png

        In the last section, you learned of some essential practices and tips for software development.  We continue those topics in this section, covering inline functions, reading and writing flash memory (including User Page flash), and how to avoid buffer overruns.  The potential issues of typcasting is discussed, and how configuration locks might help when things go really south.

         

        Code Well, and Everything Works Better -- Continued

        (See the prior section for parts 1 through 8.)

         

        9. The implications and limitations of inline functions
        If you define a function with the keyword “inline” in front of it, you are telling the compiler that you want that function to be copied into code as if it were typed into the code directly rather than as a function. As the programmer, you hope that this will speed up the code execution time. Whenever a function is called in C, the contents of the input parameters are placed onto the stack together with the return address where the code is supposed to jump back to when the function is done, and then the code jumps to execute the function. By making a function inline, you intend to bypass that time shuffling data to and from the stack. However, the C compiler takes this more or less as a suggestion, as small functions will be automatically inlined by the compiler. The compiler will ignore this inline suggestion for larger functions that the compiler has determined will see little benefit from the speed up.


        Inline functions cannot contain locally static variables. Since the function contents are intended to be replaced within the code, a static variable that is private to a function scope has no meaning. Inline functions have no scope.


        10. The difference between reading and writing flash memory
        To read data from any region of flash memory in EFM32 is very simple. Flash can be accessed with a pointer to a memory address like so:

        volatile uint8_t* address = 0x1000;
        uint8_t my_value = *address;


        By placing the asterisk in front of the address variable, you are dereferencing the pointer, which gives you the byte sized contents of system address 0x1000. The number of bytes retrieved from flash will vary based on the type of variable for my_value. For example, my_value defined as a uint64_t will fetch eight bytes at once from address 0x1000 to 0x1007. Therefore, executing my_value++ as a 64-bit unsigned integer points to 0x1008.


        Writing to flash memory is a much trickier business. All flash memory organizes memory into “pages” that group writeable cells together into big chucks that can only be erased together as a group. Therefore, if you want to write to a single byte of non-volatile memory, you have to erase the entire page first. To the programmer, that means that you have to first read the contents of memory for the whole page, store it in RAM, erase the page, then write back the whole page together with the modified byte, hoping that you don’t lose power in the middle of the operation or else you just lost that page of data forever. There are usually timing implications too, because erasing and writing a page takes a lot more time than reading a value. When a page of flash is erased, all of the contents of the page are reset to 1’s, or 0xFF’s in a byte-sized memory viewer. Therefore, when you write a byte to memory, you are simply clearing those bits that are not 1’s.


        To write to EFM32 flash, another requirement is that the function that is writing to flash must be resident in RAM, not in flash where normal executing code is located, while the same code is being executed. This requires that any function that intends to write to flash memory that is also being executed is declared with the RAMFUNC declaration as defined in the ramfunc.h library utility file.


        11. Store persistent data in flash User Page flash
        Main flash memory starts at address zero and is capped at different capacities based on the EFM32 model. See the Reference Manual for your specific chip to find out where main flash memory ends. In addition to the built-in flash, there is a memory region set aside for external flash expansion that resides just beyond the internal main memory, up to 24 MB. Any of the operations to read or write main flash memory covered in the previous section can be used to access main flash memory. However, every time that the chip is programmed, or if the device is erased, the data stored in the region used by your program will be overwritten by you new code. Therefore, reading or writing to main flash is typically used when performing a flash update procedure, for example, in a bootloader. You can of course feel free to use flash memory that is located above the program size as long as you realize that it will be erased if the chip is ever erased with a device erase operation or JTAG programming sequence.


        The EFM32 provides a separate page of flash memory that is not erased with a device erase operation or JTAG programming operation. This region of flash memory is called the User Page and starts at address 0x0FE00000. The amount of memory available is a single page, and the size of a flash page varies according to the model of EFM32 chip. Check the Data Sheet or Reference Manual for the size of a flash page. You can use this page for anything that you want to persist on the device from boot-to-boot that is safe from device erase operations, such as device serial numbers, device calibration data, or any data that is specific to each device.

         

        12. Avoid buffer overruns and the havoc those can cause
        When developing code with a desktop computer, you are lucky to be developing on a system that has an operating system, a built-in file system, and display screen. When you run into trouble with your software, the OS springs into action to rescue you and inform you that you tried to access memory that was outside the bounds of your program, or that you did something that created a fault. You have few luxuries when developing embedded code. There is no OS to watch over things to make sure that your program stays within some nicely defined boundary. If your code decides to write data to address zero or one million, the MCU will try to do what it is told. It cannot tell you that you don’t know what you are doing because there are no limits to what your program can do. You are all powerful, which is a good and a bad thing.


        A big problem that embedded developers face is buffer overruns. Working in embedded means lots of direct manipulation on memory addresses. If your program starts to behave erratically or you see a variable that seemingly changes its value without being set, a buffer overrun could be the culprit.


        The first thing to watch out for is obvious but still happens to the best of us. If you define an array that is x bytes long, don’t write beyond x-1. If there are x items in an array called foo, you can only address foo[0] to foo[x-1]. The bad thing is that if you address foo[x], foo[x+1], or so on, your code still works sometimes. The MCU will happily comply and write over whatever other variable happens to reside just beyond foo[x-1]. Then your project starts to act funky.  

         

        This can also get out of control whenever you are adjusting pointers, while casting the pointers to a different type. For example:

        void some_function()
        {
        // Array of just 4 bytes
        uint8_t my_array[4];
        
        // Pointer to group of 4 bytes
        uint32_t * my_ptr = (uint32_t *) my_array;
        // foo is assigned all 4 bytes of my_array, 
        int foo = *my_ptr;
        
        // then my_ptr is incremented by one
        // which to my_ptr's type means 4 bytes
        // So my_ptr is now sitting out of bounds
        my_ptr++;
        }

        The above demonstrates how important it is to be careful when interpreting structures that are typed a one way at creation, but then later used by pointers that are typed differently.  Just because you can do the type cast, doesn't mean that you should do it.


        Another way that you can get into trouble with buffer overruns is by forgetting about the limitations of local scope. In C, most of the work done by functions is performed on pointers that are passed into the function. The return value from a C function is often limited to a “status” byte because simple values are easily returned from C functions on the stack, while other structures are trickier to return. Take for example a simple array that is local to a function:

         

        // This function will generate a compiler warning:
        // warning: function returns address of local variable
        int * some_function()
        {
        int my_array[4];
        return my_array;
        }
        // This function has no warning!
        int * some_other_function()
        {
        int my_array[4];
        int * foo = my_array;
        return foo;
        }


        As soon as the function returns, the memory allocated for my_array will be reclaimed by the system and allocated for the next thing that needs a chunk of memory. If you return a pointer to a local array from a C function, the compiler won’t warn you, and the MCU will happily do what you tell it to do. Once again, the code will work sometimes if there happens to be no new memory allocations immediately after the function returns. Your solution will be intermittent, and you will curse the day you ever decided to work on an embedded project!

         

        // This is a better way, pass pre-allocated pointers into the function
        // “int my_array[]” and “int * my_array” are identical for function parameters
        void some_new_function(int my_array[])
        {
        my_array[0] = 1;
        }


        13. Become a type-casting master
        When working “close to the metal” as you do in embedded development, you are constantly working with small bits of information, so you end up converting from one type to another often, with limited variable sizes such as 8-bit registers. Some of the examples of this section have shown how you must be aware of the impact on addresses when incrementing a pointer on a uint8_t type versus a uint32_t type. In addition, you must also be aware of how the C compiler interprets and converts types.

         
        The casting of types from one type to another doesn’t actually perform any conversion. It’s not a function, but is just a way to tell the compiler how data should be interpreted. It seems that the C compiler should warn you whenever you are assigning variables of different types to one another, but the compiler doesn’t always warn you of this. For example, consider the case of converting signed integers to unsigned integers as in the following:

        int8_t a = 0;   // Range is -128 to +127
        uint8_t b = 0;  // Range is 0 to 255
        long c = 0;     // Range is huge, both positive and negative
        a = -64;        // a can be negative
        b = 64;         // b can only be positive
        c = a + b;      // c is 0
        a = b;          // Converting a signed to unsigned is OK, in this case
        c = a - b;      // c is again 0
        b = 128;        // b is now bigger than a can represent
        a = b;          // a is now converted to -128
        c = a - b;      // c = -128 - 128 = -256!


        This example shows how you can get into trouble as your variables grow in size. Things work OK for a while, but then they can start to fail as the range of your variable is exceeded. Be sure you know what you want when converting types. A uint of size 8 can’t hold anything more than 0 to 255, and an int of 8 bits can’t hold anything beyond -128 to +127. Simply casting a large uint8_t as an int8_t doesn’t magically change your int8_t into a larger int. It is still limited to +127. You will have to use a larger type like int16_t if you want to increase its range.


        14. Use configuration locks to mitigate issues with malfunctioning code
        Sometimes when embedded engineers do math on pointers, we wander far off course and impact hardware with our lousy software. One way to protect your code from doing something downright awful thanks to buffer overruns and other lousy pointer arithmetic is to use configuration locks. The configuration locks available on many of EFM32’s peripherals require a special value to be written to a configuration lock register in order to allow changes to be made to the peripheral configuration. This prevents misbehaving code from changing the overall configuration of the device.


        Keys to Coding Success

         

        • Start with an existing example, then document your changes with well-named variables and lots of comments.
        • Learn more about the C language and understand where and how to declare variables and functions.
        • Don’t try to write to flash memory that hasn’t been erased or to memory that is outside the scope of what your variables can access.

        In the next section, we will learn how to better control software builds in the Simplicity Studio IDE.

      • Essential Best Practices and Debugging Tips for EFM32 Project Success - Part 3

        lynchtron | 02/37/2017 | 03:17 PM

         debug_teaser.png

         

        In the last section, you learned about a few essential tips to help you develop your hardware prototype.  In this section, many hard-learned lessons in software development are shared.  What are the keywords extern, static and volatile all about?  Should you be using recursion or malloc() in your code?  Read on and find out.

         

        Code Well, and Everything Works Better


        1. Find existing software examples for your hardware devices
        The first step in developing any embedded solution is to find examples that can make your task easier. The software examples that you find for your specific parts in your custom solution will help you “see” the device in another light and help you reinterpret the device spec, even if those examples are for another computer architecture or software language.


        2. Code for the compiler
        There is no perfect computer software language. All languages have their strengths and weaknesses. The software language utilized in Simplicity Studio for the EFM32 family is C. The C language has been around long enough that it is well trusted and performs well on embedded designs, but it can be difficult to master the syntax and its idiosyncrasies. When you are coding in C, you are actually writing instructions that are meant for the compiler and other build tools. Keep that in mind. The C language is “close to the metal” in that your code has only a few steps between the code that you write in a human-readable format, the assembly code, and the binary image that is the result of the build process.


        The C code has strict typing that requires certain variables match well enough to perform safe assignment. This is there to protect you from doing stupid things, like comparing the address of a variable (i.e. a pointer) with the contents of a variable. But often in embedded development, you need to be able to convert pure numbers into addresses in order to specify a register address. This requires that you become well acquainted with type casts in order to tell the compiler that you really do know what you are doing.


        3. Use descriptive variable and function names
        The single best thing that you can do to help make sure that your code is well designed is to use descriptive variable and function names. There is no runtime performance penalty associated with long names in C code. All identifiers are removed when the build tools translate the C code into binary machine code. Consider the following snippet of code found in the FAT Filesystem (FF) library:

        	res = dir_sdi(dj, 0);
        	if (res == FR_OK) {
        		do {	/* Find a blank entry for the SFN */
        			res = move_window(dj->fs, dj->sect);
        			if (res != FR_OK) break;
        			c = *dj->dir;
        			if (c == DDE || c == 0) break;	/* Is it a blank entry? */
        			res = dir_next(dj, 1);			/* Next entry with table stretch */
        		} while (res == FR_OK);
        	}


        The above code has some comments, which certainly help and are a very good thing, but it’s hard to know the exact reason for this code by just looking at the variables, functions, enumerations and preprocessor symbols. Consider the following code as an alternative:

        // Load the first target_directory entry without table stretch
        result = set_directory_index(target_directory, NO_TABLE_STRETCH)
        if (result == FAT_RESULT_OK) { // Look for a blank entry for the Short File Name over all directories do
        { result = find_next_window_offset(target_directory->file_system_object, target_directory->current_sector); if (result != FAT_RESULT_OK) break; // Window offset was OK, check the entry short_file_name = *target_directory->short_file_name; // Is it a blank or unused entry? if (short_file_name[0] == DELETED_DIRECTORY_ENTRY_BYTE || short_file_name[0] == UNUSED_DIRECTORY) break; // Get the next entry with table stretch result = get_next_directory(target_directory, TABLE_STRETCH); } while (result == FAT_RESULT_OK);
        }


        Yes, the code is a bit wider and harder to type, but Simplicity Studio offers code completion with the CTRL+Spacebar keyboard shortcut, and you can always cut and paste. What you will gain by this is readability that requires less hunting around for what the variable names are intended to do. We can tell just by looking at the second example that this code is intended to look through a target directory and break when it finds a deleted (previously populated but now available) or zero (never populated) Short File Name entry in the target directory. The descriptive names should allow you to read aloud the code as if it were a good story, telling you the purpose as you read along.


        4. Use comments religiously
        A good software developer adds lots of comments to the code in several key places. The comments, like long variable names, don’t add to the runtime file size of the resulting executable binary and are simply there to help document the purpose of code. The top of each file in the solution should state its purpose of that file, and there should be lengthy comments at the top of each function stating the purpose of the function as well as describe the inputs and outputs. Beyond these key places, comments should be used on a line-by-line basis wherever the intention of code isn’t clear. The use of descriptive variable names can help explain the purpose of code and make comments less necessary, causing the comments that are there to stand out. Trust me, even you won’t remember the purpose of the code a year after writing it, so comment liberally!


        5. Use the emlib library
        For the EFM32 programmer, the emlib library is your friend. Use these library calls wherever you can to interface with an EFM32 peripheral. These libraries are well-tested and have additional helper code to look for problems rather than just tweaking registers directly. For example, the following code uses the emlib library:

        TIMER_TopSet(TIMER3, 1000);

        The same thing can be done by addressing the registers of a memory-mapped peripheral by a preprocessor definition, which defines TIMER3 as 0x40010C00. We don’t use this address, which would be hard to remember, but that is where the TIMER3 is mapped in main memory.

        TIMER3->TOP = 1000;


        All peripherals are mapped to memory addresses in exactly the same way, so you will sometimes see examples using this pointer notation rather than the emlib library functions. If you will look inside of the TIMER_TopSet function definition in em_timer.h, you will see that the function does exactly the same thing as this example, so in this case the library function has provided no added value. However, with the emlib library, you will sometimes get much more functionality than the simple manipulation of a mapped register. For example, the CMU_ClockEnable function takes care to make a lot of decisions on your behalf before finally using a “bit band” command to ensure that a register bit is atomically set. Use these library functions as frequently as possible to gain the benefit of all of the EFM32 library designer’s hard work.


        6. Where and how to define variables to avoid problems with the stack and heap
        There are many facets of C that are not obvious to the casual programmer, but become important as you run your code in an embedded design. For starters, all locally-declared variables go on the stack. These are the variables that you define inside functions or any block of code.

         

        The stack is a region of memory that starts at the “top of memory” or the highest available address in physical RAM, and then counts downward until you hit the stack limit. If you define too many local variables or if your code dynamically creates those variables by using recursion or other nested functions, you can run out of stack space.

         

        Global variables are those that are defined outside of all functions and other blocks of code at the module level.   The compiler automatically allocates memory for your globally-declared variables on the heap, which is part of the main memory pool outside of the stackand will generate a compiler error if you try to allocate too much RAM.  However, the use of the malloc() command in your code allocates RAM on the heap at runtime, dynamically.

         

        The use of recursion or the malloc() command on an embedded processor with limited RAM is a risky business! You must understand how many recursive attempts (or malloc() calls) your code will ever require in order to resolve and then design a solution that will never run out of stack space.

         

         

        If you define all variables in your code and let the compiler determine how to manage memory automatically, you will run into fewer issues of overrunning the stack or heap.   Even with this precaution, if your code is nearly the size available RAM when you compile and build your code, you will need to learn how to monitor the size of the stack and heap, which is beyond the scope of this section.

         

        int foo;  			// Global variable, memory is on the heap
        
        void some_function()
        {
        	int bar;		// Local variable, memory is on the stack
        }



        7. The difference between global static variables and local static variables
        Variables that are defined using keyword “static” means different things at different scopes. Inside functions, the static keyword is used in front of a variable that is to remember its value in-between calls to the function. It’s sort of “sticky” in that you can initialize it at the first invocation of the function, and then it keeps its value rather than being reinitialized every time the function executes like non-static variables. At the global scope, all variables are “sticky” in that they only get initialized once at the start of runtime and then remember their values after that. However, the static keyword placed in front of global variable indicates to the compiler that the variable is local to that module and not to be used by outside modules. This is a totally different meaning for the same “static” keyword.

        int foo1 = 1;  	// Global variable, initialized only once
        static int foo2 = 2; 	// Global variable, initialized only once, private to this module
        
        void some_function()
        {
        	int bar1 = 3;	// Local variable, initialized every time the function is called, 
        			// private to this function
        	
        	static int bar2 = 4;// Local variable, initialized only the first time that this function
        			     // is called, private to this function
        	
        	int foo1;	// This is a bad idea.  Local foo1 overrides global foo1 and makes the 
        			// global version unavailable inside this function
        }

         

        8. The meaning of volatile and extern and how they impact each other
        As long as variables and functions are not declared as static within a module, they can then be used external to that module and used in other modules. In order to tell the compiler that you intend to use the same variable across modules, you define a variable the normal way in one module, and add the keyword “extern” in front of the definition in all other modules across the design. Now, all modules in your design can gain access to the same variable. However, if one of the other modules in the design intends to modify the value of a variable outside of the place where it is originally defined, you must add the keyword “volatile” in front of that variable. This volatile keyword tells the compiler that the variable can be changed outside the module’s knowledge and prevents the optimizer from removing statements that seem to have no effect.

        // Module A
        int foo;  		// Meant to be modified in this module only
        volatile int bar;	// Value can change unexpectedly outside of this module 
        // Optimizer must always evaluate the value of bar
        
        
        // Module B
        extern int foo;	// We can read this value defined in Module A, but should not modify it
        extern int bar;	// Since declared volatile in Module A, we can read and modify this variable


        In addition, the use of volatile is hugely important when you use a Release build versus a Debug build. The compiler will actively try to squash unnecessary code as the optimization settings increase. This means that you need to prevent the compiler from doing that by using the volatile keyword on any variable that can change outside of the current scope.

         

        In the next section, we will continue down the best practices for software path and learn about inline functions, how to work with flash memory, configuration locks and how you can get into trouble with buffer overruns.