マイコン内蔵の温度センサとADCを利用する!
Kinetis K64バラツキを検証

Kinetis K64の温度センサとADCを利用する

マイコンに温度センサが搭載されているので、ちょっとした温度計測に使えるのかと思い、早速動かして見ました。

今回は、温度センサは出力インピーダンスが高そうなので、マイコン自身のデジタルノイズなどによって温度がバラついてしまいそうなので、ちょっと検証して見ます。

やりたいこと

マイコンに内蔵されている温度センサは、基本的に使えるのでしょうか?一般的には、マイコンに内蔵されているとデジタルノイズで測定温度にノイズが乗ったりします。

マイコンの温度測定や周囲の環境温度などの測定に使用できるのか確かめてみる。

測定環境

デバイス

今回使用するのは、NXPマイコンのK64を使います。

評価ボード

評価ボードは、FRDM-K64Fを使用します。少し古いデバイスですが、まだまだ現役バリバリのマイコンです。

ソフトウェア

MCUXpresso SDKを下記のURLからダウンロードして使います。

部屋の温度

部屋の温度:25℃

ソースコード

ソースコードは、Githubからダウンロードできます。

ソースコードはこちらから→https://github.com/mcuthings/k64-adc

基本は、MCUXpresso SDKのサンプルコード(ADCのポーリング)を利用して少々書き換えて使用しています。

こういったSDKのドライバーサンプルを使用すると、直ぐに動作させることができます。

ADCを使ってADC内蔵温度センサで温度を測定してみます。ADCの入力チャンネルには、温度センサ出力を選択して測定します。

測定は、5000回測定し、最小値、最大値、平均と標準偏差を取ります。

結果をprintf()で出力して確認します。

 最小値、最大値は、CMSIS-DSPライブラリを使用しています。また、標準偏差もCMSIS-DSPライブラリで利用可能なのですが、私が知っている標準偏差とは算出方法が異なるようで、計算結果も大きくなり、なんだかよく分からない数値が算出されます。数学的な計算式は、なにやら難しい計算式を用いているようですので、ここでは一般的に知られている標準偏差の計算関数を作成して計算しています。

#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
}

上記のコードでは、1)ADCのコンバージョン完了をポーリングして確認する場合と、2)コンバージョン完了までスリープして、完了時に割り込みで復帰しADCの読み込みをする場合、と2通りの取得方法を確認しています。

INTERRUPTかPOLLINGのdefineで1を設定してどちらかを実行するか選択できます。

#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

測定結果

ADC変換完了に割り込みを利用した場合

ADCの変換完了待ち受ける際に、ADC変換完了による割り込みが発生するまで、コアはスリープにした場合です。敏感な温度センサのADCの値を読み取る時に、静まり返った状態です。

標準偏差は、3.4と非常に低いことが分かります。

最大値、最小値も約±1しかバラついていません。

ADC割り込みを利用した読込み
ADC割り込みを利用した読込み

 ADC変換完了をポーリングにより検出した場合

ADCの変換完了にポーリングを利用した場合です。これは、コアはポーリングしますので、ADCが温度センサの値を読み込む時、隣でうるさい奴がいる状況ですね。

こちらは、平均値は、測定した時間が異なるので、少し大きく(温度は低く)なっています。

しかしながら、注目すべきは標準偏差です。

標準偏差は5.17と約1.7倍になりました。最大値と最小値も約±2と大きくなりました。

周りに騒ぐものがいると、やはり読み取り誤差が大きくなるようです。

ADC変換完了を検出するのにポーリングを使用した場合
ADC変換完了を検出するのにポーリングを使用した場合

まとめ

ADCの読込み時にコアなどが動作していると、温度センサーなどのような出力インピーダンスが高い出力を検出する場合には、検出結果にノイズが乗りバラツキが大きくなることが分かりました。

マイコン内蔵の温度センサは、ユースケースにもよりますが、読込み方を工夫するとバラツキを抑えて出力をADC変換できることが分かりました。

でも、マイコン内蔵のADCは、もちろんマイコン自体の温度によって温度が変化するので、周囲環境の温度センサとして使用する場合には、一定の係数などを掛けて予測値としてするなどの工夫が入りそうです。