1. 程式人生 > >STM32L0 讀取晶片溫度與當前供電電壓 STM32L051C8T6

STM32L0 讀取晶片溫度與當前供電電壓 STM32L051C8T6

在做低功耗產品的時候讀取晶片溫度和當前電壓是十分重要的一件事情。

通過當前供電電壓可以知曉電池電量是否低於水平值實現電池缺電報警。

讀取晶片溫度也很重要,可以在使用內部振盪器的時候通過校準演算法根據溫度變化來實現實時校準晶片(另外文章有介紹)。

如果不使用或儘量少使用外部元器件來實現這兩個功能是擺在我們面前一個很重要的事情,遺憾的是現在網上的資料非常混亂,基本上直接使用總有點那麼彆扭。

在這裡hoowa告訴你測試有效的方法。

如何讀取晶片電壓:

當然肯定是需要用ADC了。不過這個時候需要有一個參考電壓作為比對,很多人提出在外面使用一組LDO實現參考電壓,那樣其實LDO本身也有一定能耗,在我們追求極致低消耗的時候也不適合。

當然很多人說了為什麼不使用PVD來做,那麼我來說說,PVD本身是做電壓曲線檢測的,如果你要求低於2.5V就報警,你會發現如果你啟動電壓< 2.5V的時候無法檢測出來。所以還是自己做把。

還好STM32L0微控制器在內部有一個核心電壓,並且有一個暫存器VREFINT_CAL值可以用作基準參考從而計算出相當比較精準的電壓。經過hoowa測量,誤差還是在0.0X V左右的基本滿足電池供電檢測的要求。

不過網上很多資料用參考值1.224作為核心電壓量,其實這樣不夠精準,因為實際核心電壓是有偏移的。而且就VREFINT_CAL這個暫存器,取出來以後要通過運算得到mV級電壓情況。

但是運算公式原理很簡單,通過VDDA接入實際電池供電,對比晶片核心相對穩定的1.2xV電壓,實現檢測當前實際輸入電壓,可惜的是網上公式很混亂。

還好!!還好的是,其實STM32L0 的HAL級庫中已經存在運算公式巨集了,直接用就好了。

如何讀取晶片溫度:

在STM32L0下,只要讀到當前電壓,配合溫度暫存器,就可以取得當前溫度了。當前溫度誤差比較大,即使高精度取樣,為了節省運算時間,誤差還是存在+ / - 3度的情況,因此這個地方需要寬泛一些。

好,廢話少說上程式碼:

//請使用HAL級庫啊,不對,反正STM32L0也沒有STDLIB庫
#include "stm32l0xx_ll_adc.h" //需要這個庫實現公式計算

//初始化
void init_adc1(void)
{
    ADC_ChannelConfTypeDef sConfig;

    hadc.Instance = ADC1;
    hadc.Init.OversamplingMode = DISABLE;
    hadc.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV1;
    hadc.Init.Resolution = ADC_RESOLUTION_12B;//ADC_RESOLUTION_12B;
    hadc.Init.SamplingTime = ADC_SAMPLETIME_160CYCLES_5; //160.5cycles如果低於39.5cycles溫度取樣精準度不夠
    hadc.Init.ScanConvMode = ADC_SCAN_DIRECTION_FORWARD;
    hadc.Init.DataAlign = ADC_DATAALIGN_RIGHT;
    hadc.Init.ContinuousConvMode = DISABLE;
    hadc.Init.DiscontinuousConvMode = DISABLE;
    hadc.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
    hadc.Init.ExternalTrigConv = ADC_SOFTWARE_START;
    hadc.Init.DMAContinuousRequests = DISABLE;
    hadc.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
    hadc.Init.Overrun = ADC_OVR_DATA_PRESERVED;
    hadc.Init.LowPowerAutoWait = DISABLE;
    hadc.Init.LowPowerFrequencyMode = DISABLE;
    hadc.Init.LowPowerAutoPowerOff = DISABLE;
    if (HAL_ADC_Init(&hadc) != HAL_OK)
    {
        _Error_Handler(__FILE__, __LINE__);
    }

    /**Configure for the selected ADC regular channel to be converted. 
    */
    sConfig.Channel = ADC_CHANNEL_VREFINT; //初始化VREFINT_CAL參考電壓
    sConfig.Rank = ADC_RANK_CHANNEL_NUMBER;
    if (HAL_ADC_ConfigChannel(&hadc, &sConfig) != HAL_OK)
    {
        _Error_Handler(__FILE__, __LINE__);
    }

    sConfig.Channel = ADC_CHANNEL_TEMPSENSOR; //初始化晶片溫度感測器
    sConfig.Rank = ADC_RANK_CHANNEL_NUMBER;
    if (HAL_ADC_ConfigChannel(&hadc, &sConfig) != HAL_OK)
    {
        _Error_Handler(__FILE__, __LINE__);
    }
}

//讀取adc1值
uint16_t readchannel_adc1(uint32_t Channel)
{
  ADC_ChannelConfTypeDef adcConf;
  uint16_t adcData = 0;

    /* wait the the Vrefint used by adc is set */
    while (__HAL_PWR_GET_FLAG(PWR_FLAG_VREFINTRDY) == RESET) {};

    //啟動CLK時鐘
    __HAL_RCC_ADC1_CLK_ENABLE();

    /*calibrate ADC if any calibraiton hardware*/
    HAL_ADCEx_Calibration_Start(&hadc, ADC_SINGLE_ENDED );

    /* Deselects all channels*/
    adcConf.Channel = ADC_CHANNEL_MASK;
    adcConf.Rank = ADC_RANK_NONE; 
    HAL_ADC_ConfigChannel( &hadc, &adcConf);

    /* configure adc channel */
    adcConf.Channel = Channel;
    adcConf.Rank = ADC_RANK_CHANNEL_NUMBER;
    HAL_ADC_ConfigChannel( &hadc, &adcConf);

    /* Start the conversion process */
    HAL_ADC_Start(&hadc);

    /* Wait for the end of conversion */
    HAL_ADC_PollForConversion( &hadc, HAL_MAX_DELAY );

    /* Get the converted value of regular channel */
    adcData += HAL_ADC_GetValue(&hadc);

    __HAL_ADC_DISABLE(&hadc);

    __HAL_RCC_ADC1_CLK_DISABLE();

  return adcData;
}

//具體呼叫程式碼
void main(void)
{

    init_adc1(); //完成初始化
    uint16_t vdda_mV = __LL_ADC_CALC_VREFANALOG_VOLTAGE(readchannel_adc1(ADC_CHANNEL_VREFINT),LL_ADC_RESOLUTION_12B); //取得當前VDDA的電壓,單位mV

    uint16_t temp_degress = __LL_ADC_CALC_TEMPERATURE(vdda_mV,readchannel_adc1(ADC_CHANNEL_TEMPSENSOR),LL_ADC_RESOLUTION_12B); //取得當前的溫度,單位攝氏度
}