Analog to Digital Converter (ADC)

  1. Configuring the ADC
  2. Performing a Single ADC conversion
  3. Single Conversion Full Code Example
  4. Performing a scan ADC conversion
  5. Scan Conversion Full Code Example

Configuring the ADC

This section shows how to configure the ADC itself and the ADC pins PD5 and PD6 on the extension bus. The ADC module of the low level Emlib API is used, especially since there is no higher level API available currently.

#include "em_adc.h"

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

FunctionDescription
ADC_Init()This function is called to initialize the ADC module of the MCU. This involves only configuring the ADC itself. The pin configuration is not covered by this function.
ADC_InitSingle()This function is called to initialize performing only a single conversion on one or multiple ADC input pins.
ADC_InitScan()This function is called to initialize performing a scan conversion on multiple ADC input pins.
ADC_Start()This function is used to start the ADC conversion on the configured ADC channel.
ADC_DataSingleGet()This function is used to read the data from the performed single conversion of one ADC channel.
ADC_DataScanGet()This function is used to read the data from multiple ADC channels performed by a scan conversion

In addition to the interface em_adc.h, the following interface will be needed as well.

#include "em_gpio.h"
#include "BSP_BoardShared.h"

The interface em_gpio.h will be used to configure the used GPIO pin as input with additional configuration, such as a pull- up or pull-down resistor. This is necessary to ensure that the pin will is properly configured to work with the internal ADC of the XDKs MCU.

Please note, that the interface BSP_BoardShared.h needs to be included manually in the XDK project’s makefile, otherwise an error will be caused during the build. For that, replace line 19 in the makefile with

BCDS_XDK_INCLUDES = \
          -I$(BCDS_BASE_DIR)/xdk110/Platform/BSP/source/

to add BSP_BoardShared.h to the known includes.

The code below shows how to configure the ADC pin PD5 as input and with a pull-down resistor. This ensures that, if no external device is attached or active, the input will always be zero.

GPIO_PinModeSet(gpioPortD, 5, gpioModeInputPull, 0);
GPIO_PinOutClear(gpioPortD, 5);

Please note that this configuration must be made for every used ADC pin. Otherwise, data cannot be read from the respective ADC pin or pins.

Performing a Single ADC conversion

Now that pin is configured, the configuration for the ADC itself and the specific ADC channel can be done. Since the ADC is already configured during the startup of the XDK. To do it manually, the structure ADC_Init_TypeDef needs to be configured and then passed to the ADC_Init() function. For more information about the startup configuration of the ADC, please refer to the function Board_ADC_init() in the interface BSP_Board.c. The interface BSP_Board.c can be found in the following directory:

SDK > xdk110 > Platform > BSP > source

Now, a single conversion or a scan conversion needs to be configured on the specific ADC channel. This section will cover the implementation of the single conversion, the scan conversion is described more detailed in the subchapter Performing a scan ADC conversion.

ADC_InitSingle_TypeDef channelInit = ADC_INITSINGLE_DEFAULT;
channelInit.reference = adcRef2V5;
channelInit.resolution = adcRes12Bit;
channelInit.input = adcSingleInpCh5;
ADC_InitSingle(ADC0, &channelInit);

The single conversion uses the structure ADC_InitSingle_TypeDef, where the ADC channel, the resolution on the channel and the reference voltage are configured. Additional settings can also be made, but the ones used in the code above are the essential settings. Please refer to the complete definition of the structure ADC_InitSingle_TypeDef in em_adc.h for more options. In the above, the default values are configured by using the macro ADC_INITSINGLE_DEFAULT. Finally, the single conversion can be initialized by calling the function ADC_InitSingle().

After initializing the conversion, the conversion has to be started and the data can be read afterwards (after a brief delay). An example for reading the data is seen in the code below.

uint32_t AdcSample = 0;

while ((ADC0->STATUS & (ADC_STATUS_SINGLEACT)) && (BSP_UNLOCKED == ADCLock));

__disable_irq();
ADCLock = BSP_LOCKED;
__enable_irq();
ADC_Start(ADC0, adcStartSingle);

// Wait while conversion is in process
while (ADC0->STATUS & (ADC_STATUS_SINGLEACT));

AdcSample = 0xFFF & ADC_DataSingleGet(ADC0);
__disable_irq();
ADCLock = BSP_UNLOCKED;
__enable_irq();

First, the variable AdcSample is declared, where the read ADC value will be stored later. Afterwards, the function actively waits until no conversion is currently active. This is indicated by the bit ADC_STATUS_SINGLEACT.

Then, the functions __enable_irq() and disable_irq() are used to temporarily enable and disable interrupts used on the GPIO pins and the ADC is locked for a conversion. Afterwards, a single conversion is started. When the sampling conversion is finished, the value of ADC_STATUS_SINGLEACT is set to one. The function actively waits for this using a while-loop. Then, the received values first 12 bits are stored in AdcValue. Afterwards the interrupts for the GPIO pins are disabled, the ADC lock is opened and the GPIO pin interrupts are enabled again.

Single Conversion Full Code Example

Note: The full code example is intended for XDK-Workbench versions 3.4.0 and higher.

/* --------------------------------------------------------------------------- |
 * 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_adc.h"
#include "em_gpio.h"
#include "BSP_BoardShared.h"

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

static CmdProcessor_T * AppCmdProcessor;
static xTaskHandle AppControllerHandle = NULL;

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

static void AppControllerFire(void* pvParameters) {

    BCDS_UNUSED(pvParameters);

    uint32_t AdcSample = 0;

    while(1) {

        while ((ADC0->STATUS & (ADC_STATUS_SINGLEACT)) && (BSP_UNLOCKED == ADCLock));
        __disable_irq();

        ADCLock = BSP_LOCKED;
        __enable_irq();
        ADC_Start(ADC0, adcStartSingle);

        // Wait while conversion is in process
        while (ADC0->STATUS & (ADC_STATUS_SINGLEACT));
        AdcSample = 0xFFF & ADC_DataSingleGet(ADC0);

        printf("Adc Single Sample %u \n\r",(unsigned int) AdcSample);

        __disable_irq();
        ADCLock = BSP_UNLOCKED;
        __enable_irq();

    }
}

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

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

    BCDS_UNUSED(param1);
    BCDS_UNUSED(param2);

    ADC_InitSingle_TypeDef channelInit = ADC_INITSINGLE_DEFAULT;
    channelInit.reference = adcRef2V5;
    channelInit.resolution = adcRes12Bit;
    channelInit.input = adcSingleInpCh5;
    ADC_InitSingle(ADC0, &channelInit);

    if (RETCODE_OK == retcode)
    {
        if (pdPASS != xTaskCreate(AppControllerFire, (const char * const ) "AppController", TASK_STACK_SIZE_APP_CONTROLLER, NULL, TASK_PRIO_APP_CONTROLLER, &AppControllerHandle))
        {
            retcode = RETCODE(RETCODE_SEVERITY_ERROR, RETCODE_OUT_OF_RESOURCES);
        }
    }

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

static void AppControllerSetup(void * param1, uint32_t param2) {

    BCDS_UNUSED(param1);
    BCDS_UNUSED(param2);
    Retcode_T retcode = RETCODE_OK;

    GPIO_PinModeSet(gpioPortD, 5, gpioModeInputPull, 0);
    GPIO_PinOutClear(gpioPortD, 5);

    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);
    }
}

Performing a scan ADC conversion

This section explains how to configure the ADC to perform a scan conversion. The steps are quite similar to what has to be done for the single conversion.

ADC_InitScan_TypeDef scanInit = ADC_INITSCAN_DEFAULT;

scanInit.reference = adcRef2V5;
scanInit.resolution = adcRes12Bit;
scanInit.input = ADC_SCANCTRL_INPUTMASK_CH5 | ADC_SCANCTRL_INPUTMASK_CH6;

ADC_InitScan(ADC0, &scanInit);

As before, still referring to em_adc.h, a structure called ADC_InitScan_TypeDef is used to configure the scan conversion. The complete configuration can be set by using the default configuration defined in ADC_INITSCAN_DEFAULT.

In this example, only the reference voltage, the resolution and the input channels are configured specifically. Compared to the single conversion, this conversion has two inputs. These are set by using a bitwise OR operation with controller specific defines for channel 5 and 6 on the extension bus. Please note that a configuration, as implemented in the code snippet above, is still necessary for each of the used ADC pins. Afterwards the configuration is done and the scan conversion is initialized by passing the configuration into the function ADC_InitScan().

Reading is done similarly as it was done for the single conversion, except that two channels are started and read simultaneously, as shown in the code below.

uint32_t AdcScanSample[] = {0,0};

while ((ADC0->STATUS & (ADC_STATUS_SCANACT)) && (BSP_UNLOCKED == ADCLock)) {};

__disable_irq();
ADCLock = BSP_LOCKED;
__enable_irq();

ADC_Start(ADC0, adcStartScan);

for ( int channels = 0; channels < 2; channels++) {
  // Wait for Valid Data
  while (!(ADC0->STATUS & ADC_STATUS_SCANDV));
  // Read the Scanned data
  AdcScanSample[channels] = 0xFFF & ADC_DataScanGet(ADC0);
}

__disable_irq();
ADCLock = BSP_UNLOCKED;
__enable_irq();

Reading data from a scan conversion follows the same steps as reading the single conversion. For reading data, an array called AdcScanSample is declared at the beginning. In this array, the ADC data from the two ADC channels is stored.

Similar as before, a verification is performed by reading the ADC_STATUS_SCANACT bit to check if a scan conversion is currently running. If this is not the case, the scan conversion can be started. Afterwards the data from the different ADC channels is stored into the AdcScanSample array in the same manner as in the code snippet above.

The only difference here is that two channels have to be read one after another. Please note that for the scan conversion it is necessary to wait until the bit ADC_STATUS_SCANDV is set. The bit itself signals that the register contains valid data as the result from the conversion of an ADC channel.

Scan Conversion Full Code Example

Note: The full code example is intended for XDK-Workbench versions 3.4.0 and higher.

/* --------------------------------------------------------------------------- |
 * 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_adc.h"
#include "em_gpio.h"
#include "BSP_BoardShared.h"

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

static CmdProcessor_T * AppCmdProcessor;

static xTaskHandle AppControllerHandle = NULL;

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

static void AppControllerFire(void* pvParameters)
{
    BCDS_UNUSED(pvParameters);

    while(1) {
      uint32_t AdcScanSample[] = {0,0};

      while ((ADC0->STATUS & (ADC_STATUS_SCANACT)) && (BSP_UNLOCKED == ADCLock)) {};

      __disable_irq();
      ADCLock = BSP_LOCKED;
      __enable_irq();

      ADC_Start(ADC0, adcStartScan);

      for ( int channels = 0; channels < 2; channels++) {
        // Wait for Valid Data
        while (!(ADC0->STATUS & ADC_STATUS_SCANDV));
        // Read the Scanned data
        AdcScanSample[channels] = 0xFFF & ADC_DataScanGet(ADC0);

        printf("Adc Scan Sample %u Channel %d \n\r",(unsigned int) AdcScanSample[channels],channels);
      }

      __disable_irq();
      ADCLock = BSP_UNLOCKED;
      __enable_irq();
    }
}

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

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

    ADC_InitScan_TypeDef scanInit = ADC_INITSCAN_DEFAULT;

    scanInit.reference = adcRef2V5;
    scanInit.resolution = adcRes12Bit;
    scanInit.input = ADC_SCANCTRL_INPUTMASK_CH5 | ADC_SCANCTRL_INPUTMASK_CH6;

    ADC_InitScan(ADC0, &scanInit);

    if (RETCODE_OK == retcode)
    {
        if (pdPASS != xTaskCreate(AppControllerFire, (const char * const ) "AppController", TASK_STACK_SIZE_APP_CONTROLLER, NULL, TASK_PRIO_APP_CONTROLLER, &AppControllerHandle))
        {
            retcode = RETCODE(RETCODE_SEVERITY_ERROR, RETCODE_OUT_OF_RESOURCES);
        }
    }
    if (RETCODE_OK != retcode)
    {
        printf("AppControllerEnable : Failed \r\n");
        Retcode_RaiseError(retcode);
        assert(0);
    }
}

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

    GPIO_PinModeSet(gpioPortD, 5, gpioModeInputPull, 0);
    GPIO_PinOutClear(gpioPortD, 5);

    GPIO_PinModeSet(gpioPortD, 6, gpioModeInputPull, 0);
    GPIO_PinOutClear(gpioPortD, 6);

    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);
    }
}