Why not use ADC and an embedded temp sensor on Kinetis K64.


There are a lot of MCU’s that embedded an temp sensor. So, I’m gonna try and get it working.

In general, a temp sensor’s output impedance tends to be relatively high, that could cause the noisy result. This time, I will verify the measurement result to see how much it varies.

What I want to do

MCU’s digital noise could influence the result of temp sensor or ADC measurement.
I want to see how much it varies and if it can be used for general purpose of temperature measurement.

Measurement environment

Device

I will use NXP Kinetis MCU, K64.

Evaluation board

There is an evaluation board available from NXP, FRDM-K64F. It is a bit old but it is still a hot device in the market.

Software

You can build and download MCUXpresso SDK from NXP MCUXpresso site.
Here is the URL to download. → https://www.nxp.com/mcuxpresso

Room Temperature

Room temp:25 Deg C

Source Code

My source code is available from GitHub. You can download it and try it by yourself.
Source code is available from here→https://github.com/mcuthings/k64-adc

Basically, I modified an sample code that comes with MCUXpresso SDK, which is an ADC polling example.
It is very handy and you can get it working very easily.

In order to measure temperature, ADC is going to be used. ADC input channel is available for the embedded temp sensor in K64 MCU.
So, I will configure the temp sensor as an ADC input channel.

The measurement will be done 5000 times and it will be calculated to the average, max, min and standard deviation.
Then, it is output by printf().

Max/Min, Std Deviation value
Max/Min value is calculated by means of CMSIS-DSP library. And there is a function for standard deviation as well, but its calculation seems be very different than what I know of standard deviation. The measurement result is different from the result of a “normal” standard deviation that I know. 

The calculation algorithm seems be very difficult, so I will use a “normal” calculation of standard deviation this time.


#include "fsl_debug_console.h"
#include "board.h"
#include "fsl_adc16.h"

#include "pin_mux.h"
#include "clock_config.h"

#include "arm_math.h"
#include "fsl_smc.h" //For low power mode
/*******************************************************************************
 * Definitions
 ******************************************************************************/
#define DEMO_ADC16_BASE ADC0
#define DEMO_ADC16_CHANNEL_GROUP 0U
//#define DEMO_ADC16_USER_CHANNEL 12U
#define DEMO_ADC16_USER_CHANNEL 26U //ch26 is for Temperature sensor output
#define NUMBER_OF_MEASUREMENT 5000
#define INTERRUPT 0 //ADC conversion wait for "INTERRUPT" should be defined to 1
#define POLLING 1   //ADC conversion wait for "POLLING" should be defined to 1

/*******************************************************************************
 * Prototypes
 ******************************************************************************/
void ADC0_IRQHandler(void);

/*******************************************************************************
 * Variables
 ******************************************************************************/
volatile bool g_Adc16ConversionDoneFlag = false;
volatile uint32_t g_Adc16ConversionValue;
uint16_t cnt=0;
/*******************************************************************************
 * Code
 ******************************************************************************/
/*!
 * @brief Main function
 */

/* Standard deviation */
float32_t dev(float32_t data[], int n) {
    int i;
    float32_t m;
    float32_t ret;
    arm_mean_f32(data, n, &m);
    float32_t var = 0.0;
    for (i = 0; i < n; i++)
        var += (data[i] - m) * (data[i] - m);
    arm_sqrt_f32(var/n, &ret);
    return ret;
}

void ADC0_IRQHandler(void)
{
    g_Adc16ConversionDoneFlag = true;
    g_Adc16ConversionValue = ADC16_GetChannelConversionValue(DEMO_ADC16_BASE, DEMO_ADC16_CHANNEL_GROUP);
    /* Add for ARM errata 838869, affects Cortex-M4, Cortex-M4F Store immediate overlapping
      exception return operation might vector to incorrect interrupt */
#if defined __CORTEX_M && (__CORTEX_M == 4U)
    __DSB();
#endif
}

int main(void)
{
    adc16_config_t adc16ConfigStruct;
    adc16_channel_config_t adc16ChannelConfigStruct;



    BOARD_InitPins();
    BOARD_BootClockRUN();
    BOARD_InitDebugConsole();

    PRINTF("\r\nADC16 polling Example.\r\n");

    /* For Very low power stop */
    SMC_SetPowerModeProtection(SMC, kSMC_AllowPowerModeAll);
    NVIC_EnableIRQ(ADC0_IRQn);
    

    /*
     * adc16ConfigStruct.referenceVoltageSource = kADC16_ReferenceVoltageSourceVref;
     * adc16ConfigStruct.clockSource = kADC16_ClockSourceAsynchronousClock;
     * adc16ConfigStruct.enableAsynchronousClock = true;
     * adc16ConfigStruct.clockDivider = kADC16_ClockDivider8;
     * adc16ConfigStruct.resolution = kADC16_ResolutionSE12Bit;
     * adc16ConfigStruct.longSampleMode = kADC16_LongSampleDisabled;
     * adc16ConfigStruct.enableHighSpeed = false;
     * adc16ConfigStruct.enableLowPower = false;
     * adc16ConfigStruct.enableContinuousConversion = false;
     */
    ADC16_GetDefaultConfig(&adc16ConfigStruct);
#ifdef BOARD_ADC_USE_ALT_VREF
    adc16ConfigStruct.referenceVoltageSource = kADC16_ReferenceVoltageSourceValt;
#endif    
    ADC16_Init(DEMO_ADC16_BASE, &adc16ConfigStruct);
    ADC16_EnableHardwareTrigger(DEMO_ADC16_BASE, false); /* Make sure the software trigger is used. */
#if defined(FSL_FEATURE_ADC16_HAS_CALIBRATION) && FSL_FEATURE_ADC16_HAS_CALIBRATION
    if (kStatus_Success == ADC16_DoAutoCalibration(DEMO_ADC16_BASE))
    {
        PRINTF("ADC16_DoAutoCalibration() Done.\r\n");
    }
    else
    {
        PRINTF("ADC16_DoAutoCalibration() Failed.\r\n");
    }
#endif /* FSL_FEATURE_ADC16_HAS_CALIBRATION */
    PRINTF("Press any key to get user channel's ADC value ...\r\n");

    adc16ChannelConfigStruct.channelNumber = DEMO_ADC16_USER_CHANNEL;
    #if POLLING
    adc16ChannelConfigStruct.enableInterruptOnConversionCompleted = false;
    #else// for Interrupt
    adc16ChannelConfigStruct.enableInterruptOnConversionCompleted = true;
    #endif
#if defined(FSL_FEATURE_ADC16_HAS_DIFF_MODE) && FSL_FEATURE_ADC16_HAS_DIFF_MODE
    adc16ChannelConfigStruct.enableDifferentialConversion = false;
#endif /* FSL_FEATURE_ADC16_HAS_DIFF_MODE */

    
    
    float32_t avg=0; //Average of Temp sensor measurement 
    float32_t std=0; //standard deviation of measurement result
    float32_t arm_std=0; //CMSIS-DSP standard deviation of measurement result
    float32_t min;
    float32_t max;
    uint32_t min_index;
    uint32_t max_index;

    float32_t data[NUMBER_OF_MEASUREMENT]={0}; //Buffer for measurement data of Temp sensor x 4096 times
    int n = sizeof(data)/sizeof(float32_t);

    while (1)
    {
        if(cnt==NUMBER_OF_MEASUREMENT){
        break;
        }
        
        #if POLLING
        /*ADC triggered by Software*/
        ADC16_SetChannelConfig(DEMO_ADC16_BASE, DEMO_ADC16_CHANNEL_GROUP, &adc16ChannelConfigStruct);
        
        /* Polling to see if ADC conversion has been done.*/
        while (0U == (kADC16_ChannelConversionDoneFlag &
                      ADC16_GetChannelStatusFlags(DEMO_ADC16_BASE, DEMO_ADC16_CHANNEL_GROUP)))
        {
        }
        g_Adc16ConversionValue = ADC16_GetChannelConversionValue(DEMO_ADC16_BASE, DEMO_ADC16_CHANNEL_GROUP);
        #endif

        #if INTERRUPT
        g_Adc16ConversionDoneFlag = false;
        /*ADC triggered by Software*/
        ADC16_SetChannelConfig(DEMO_ADC16_BASE, DEMO_ADC16_CHANNEL_GROUP, &adc16ChannelConfigStruct);

        /* Enter Partial Stop mode and wait for ADC conversion */
        /* Enter Very low power stop */
        SMC_PreEnterStopModes();
        SMC_SetPowerModeStop(SMC, kSMC_PartialStop);
        SMC_PostExitStopModes();

        while(!g_Adc16ConversionDoneFlag);
        #endif
        //PRINTF("ADC Value: %d\r\n", ADC16_GetChannelConversionValue(DEMO_ADC16_BASE, DEMO_ADC16_CHANNEL_GROUP));
        
        data[cnt]= g_Adc16ConversionValue;
                 
        cnt++;

    }

    arm_max_f32(data,n,&max,&max_index); // CMSIS-DSP library
    arm_min_f32(data,n,&min,&min_index); // CMSIS-DSP library
    PRINTF("Max value Data[%d]=%f\r\n", max_index, max);
    PRINTF("Min value Data[%d]=%f\r\n", min_index, min);

    arm_mean_f32(data,n,&avg); // Average of measured temp sensor.
    std = dev(data,n);
    PRINTF("Average: %f\r\n",avg);
    PRINTF("Standard deviation: %e\r\n",std);
    
    while(1); //end
}

In above code, there are two way to measure the temperature.

1) Polling is used to detect the conversion complete flag in ADC register,
2) ADC conversion complete Interrupt is used, and get in sleep during ADC measurement. When ADC conversion complete interrupt is generated, K64 core wakes up to read ADC value.

You can select interrupt method or polling method by the define of INTERRUPT or POLLING.

#define INTERRUPT 0   //ADC conversion wait for “INTERRUPT” should be defined to 1
#define POLLING 1   //ADC conversion wait for “POLLING” should be defined to 1

Measurement result

The case of using an interrupt when ADC conversion is done

When ADC conversion is completed, an interrupt is generated. During the ADC conversion MCU core is set in sleep as if it were silence so that you can read it without noise.

The result is about 3.4 points of std deviation as you can see below in the picture.

Max/Min value is within only +/-1 point.

Measurement result in case of ADC interrupt
Measurement result in case of ADC interrupt

 The case of using polling ADC conversion complete flag

Next is the case of using polling ADC conversion complete flag. This case is like noisy guy is next to ADC since MCU core keeps reading the register of ADC flag.

Note that the result of average shows a bit higher than the previous case, as the time was different when it was measured. I had no time to do it at the same time…

However, there is more important thing!
It is the std deviation!

It is 5.17 points and about 1.7 times higher than the interrupt case. 
Max/Min value shows within +/-2, which became higher than previous case.

You can see there is a noise if a noisy guy is there next to ADC when ADC conversion is being done.

The case of using polling ADC conversion complete flag
The case of using polling ADC conversion complete flag

Summary

When ADC conversion is being done, especially for such an high output impedance of temp sensor, it is found that the ADC measurement value shows noisy and deviates more than the case of using ADC interrupt and core sleep.

MCU’s embedded ADC and temperature can be used with suppressing the noise by means of ADC interrupt and getting MCU core to sleep during ADC conversion.

However, the embedded temp sensor in MCU is of course influenced by the temp of MCU itself. When it comes to the use-case to measure surrounding  or ambient temperature, you need to be aware that it should be compensated with a certain of co-efficient.