Pulse Counter

  1. General Information
  2. Configuring the Pulse Counter
  3. Full Code Example

General Information

A Pulse Counter can be used for counting incoming pulses on a single input or to decode quadrature encoded inputs. It is mainly used to keep track of incoming pulses or rotations. It is used to capture the input signal from resolvers or any other types of rotatory encoders for position tracking. Single input counting allows only counting incoming pulses, whereas additionally counting on a second input allows to keep track of the rotating direction.

Configuring the Pulse Counter

This section shows how to configure the pulse counter module to be used for overflow pulse counting. The EMLib API for the pulse counter module will be used, since it offers a lot of functionality which is not covered on higher API levels.

#include "em_pcnt.h"

The essential functions that are used in this guide are described in the table below. For a full description, please refer to the corresponding API documentation.

FunctionDescription
PCNT_Init()This function is called to initialize the pulse counter module of the XDK. This involves passing a struct into it with configurations for the module itself.
PCNT_IntEnable()This function is called to enable a chosen interrupt of the pulse counter module.
PCNT_IntClear()This function is called to clear the interrupt flag of the timer module.

Additionally to the em_pcnt.h interface, the following interface will be needed, too.

#include "em_cmu.h"
#include "em_gpio.h"

The code below shows how to configure and initialize the pulse counter module for overflow counting and then trigger an interrupt for the overflow counting event.

// Please insert this code in AppControllerSetup() below BCDS_UNUSED(param2);
// Select LFRCO as clock source for LFA
CMU_ClockSelectSet(cmuClock_LFA, cmuSelect_LFRCO);
CMU_ClockEnable(cmuClock_CORELE, true);
CMU_ClockEnable(cmuClock_PCNT0, true);

GPIO_PinModeSet(gpioPortC, 0, gpioModeInputPull, 0);
GPIO_PinOutClear(gpioPortC, 0);

PCNT_Init_TypeDef pcntInit = PCNT_INIT_DEFAULT;

pcntInit.mode = pcntModeOvsSingle;
pcntInit.counter = 0;
pcntInit.top = 10;
pcntInit.s1CntDir = false;

PCNT0->ROUTE = PCNT_ROUTE_LOCATION_LOC2;

PCNT_IntEnable(PCNT0, PCNT_IF_OF);

NVIC_EnableIRQ(PCNT0_IRQn);

PCNT_Init(PCNT0, &pcntInit);

The first three function calls of this code-snippet are mandatory to enable the clock for the pulse counter. CMU_ClockSelectSet() is used to set the low frequency clock for the pulse counter module. This clock will then be used by the pulse counter module to be fully operational and is enabled by passing cmuClock_PCNT0 into the function CMU_ClockEnable(). Additionally, since the pulse counter module is a low peripheral module, it is also required to enable the low power clock for it. That is done by passing cmuClock_CORELE into the function CMU_ClockEnable().

Afterwards, the configuration for input pin itself is done by using the functions provided by the em_gpio.h interface. For the pulse counter, the GPIO pin PC0 on the extension bus is configured as input with a pull down resistor.

Then, the configuration of the pulse counter module itself is done. For that, a struct of the type PCNT_Init_TypeDef is declared and filled with the default configuration using the macro PCNT_INIT_DEFAULT. Only some configuration is made for the parameters mode, counter, top, and slCnDir. The parameter mode is used to set the counting event for the pulse counter on which an interrupt is triggered. In this example, overflow counting is used. The parameters counter and top are used to set the initial counting value and the maximal counting value on which the interrupt will be triggered.

Afterwards, the route location is set to 2 to use the GPIO pin C0, as the pulse counter module can be used on six different GPIO pins of the XDK’s MCU with six different locations. Then the interrupt is enabled for overflow counting by passing PCNT_IF_OF to the function PCNT_IntEnable() and it is also activated in the interrupt controller of the XDK’s MCU by passing PCNT0_IRQn into the function NVIC_EnableIRQ().

The initialization is completed by passing the configuration struct pcntInit into the function PCNT_Init().

The last step is to define an interrupt handler for this particular pulse counter. If none is defined, the application will not process the interrupt correctly.

The implementation of an interrupt handler is shown in the following code-snippets and needs to implemented before attempting to start the pulse counter.

void PCNT0_IRQHandler(void){
    PCNT_IntClear(PCNT0, PCNT_IF_OF);
    // do something
    portYIELD_FROM_ISR(pdTRUE);
}

The signature for the interrupt handler should always be the same as it is shown in the code-snippet. The function call of TPCNT_IntClear() inside the function is used to clear the interrupt overflow flag. If the flag is not cleared, no new counting up is started. Please note that it is essential to clear this flag within the interrupt handler. Additional functionality can be added before or after the call of PCNT_IntClear().

In most use cases, it is recommended to clear the flag first before executing any other functionality. Additionally, please note that the functionality should be kept short and simple, since an interrupt event occurs periodically in very short time intervals. As such, long processing chains should be avoided. Additionally, it is necessary to call the macro portYIELD_FROM_ISR() at the end of the interrupt handler too. This macro informs FreeRTOS that the interrupt is finished, so that execution can return to the context of the task that was interrupted.

This is necessary, because the interrupt from the pulse counter module is a hardware interrupt with the highest priority and automatically interrupts the currently running operating task. To avoid issues with FreeRTOS, it is mandatory to return to the initial task after the interrupt was processed.

Afterwards, no further implementation or configuration needs to be done.

Full Code Example

/* --------------------------------------------------------------------------- |
 * INCLUDES & DEFINES ******************************************************** |
 * -------------------------------------------------------------------------- */

/* own header files */
#include "XdkAppInfo.h"
#undef BCDS_MODULE_ID  /* Module ID define before including Basics package*/
#define BCDS_MODULE_ID XDK_APP_MODULE_ID_APP_CONTROLLER

/* own header files */
#include "AppController.h"

/* system header files */
#include <stdio.h>

/* additional interface header files */
#include "BCDS_CmdProcessor.h"
#include "FreeRTOS.h"
#include "task.h"

#include "em_pcnt.h"
#include "em_cmu.h"
#include "em_gpio.h"

/* --------------------------------------------------------------------------- |
 * HANDLES ******************************************************************* |
 * -------------------------------------------------------------------------- */

static CmdProcessor_T * AppCmdProcessor;

/* --------------------------------------------------------------------------- |
 * EXECUTING FUNCTIONS ******************************************************* |
 * -------------------------------------------------------------------------- */

void PCNT0_IRQHandler(void){
    PCNT_IntClear(PCNT0, PCNT_IF_OF);
    // do something
    portYIELD_FROM_ISR(pdTRUE);
}

/* --------------------------------------------------------------------------- |
 * BOOTING- AND SETUP FUNCTIONS ********************************************** |
 * -------------------------------------------------------------------------- */

static void AppControllerEnable(void * param1, uint32_t param2)
{
    BCDS_UNUSED(param1);
    BCDS_UNUSED(param2);

    PCNT_Init_TypeDef pcntInit = PCNT_INIT_DEFAULT;
    pcntInit.mode = pcntModeOvsSingle;
    pcntInit.counter = 0;
    pcntInit.top = 10;
    pcntInit.s1CntDir = false;

    PCNT0->ROUTE = PCNT_ROUTE_LOCATION_LOC2;

    PCNT_IntEnable(PCNT0, PCNT_IF_OF);
    NVIC_EnableIRQ(PCNT0_IRQn);
    PCNT_Init(PCNT0, &pcntInit);

}

static void AppControllerSetup(void * param1, uint32_t param2)
{
    BCDS_UNUSED(param1);
    BCDS_UNUSED(param2);
    Retcode_T retcode = RETCODE_OK;

    // Select LFRCO as clock source for LFA
    CMU_ClockSelectSet(cmuClock_LFA, cmuSelect_LFRCO);
    CMU_ClockEnable(cmuClock_CORELE, true);
    CMU_ClockEnable(cmuClock_PCNT0, true);

    GPIO_PinModeSet(gpioPortC, 0, gpioModeInputPull, 0);

    GPIO_PinOutClear(gpioPortC, 0);

    retcode = CmdProcessor_Enqueue(AppCmdProcessor, AppControllerEnable, NULL, UINT32_C(0));

    if (RETCODE_OK != retcode){
        printf("AppControllerSetup : Failed \r\n");
        Retcode_RaiseError(retcode);
        assert(0);
    }
}

void AppController_Init(void * cmdProcessorHandle, uint32_t param2)
{
    BCDS_UNUSED(param2);

    Retcode_T retcode = RETCODE_OK;

    if (cmdProcessorHandle == NULL)
    {
        printf("AppController_Init : Command processor handle is NULL \r\n");
        retcode = RETCODE(RETCODE_SEVERITY_ERROR, RETCODE_NULL_POINTER);
    }
    else
    {
        AppCmdProcessor = (CmdProcessor_T *) cmdProcessorHandle;
        retcode = CmdProcessor_Enqueue(AppCmdProcessor, AppControllerSetup, NULL, UINT32_C(0));
    }

    if (RETCODE_OK != retcode)
    {
        Retcode_RaiseError(retcode);
        assert(0);
    }
}