Перейти к содержанию

Получаем температуру контроллера STM32l4 правильно

Обычная затея – считать температуру контроллера может заиграть новыми красками, когда ты внезапно получишь отрицательные/ниже комнатных значения. Что-то явно пошло не так – и это нужно исправлять. Давайте получим нормальную температуру с внутреннего датчика микроконтроллера STM32 (STM32L432 в нашем случае).

Откроем референс мануал для данного семейства RM0394 на секции с температурным датчиком.

RM0394

Блок-схема нам говорит, что к 17 каналу АЦП подключен температурный датчик, который выдает V_TS – напряжение, которое линейно (почти) зависит от температуры. Дальше нужно перевести получаемые отсчеты АЦП в градусы. Чуть ниже нам приводят формулу для рассчета температуры в градусах Цельсия. Это всего лишь каноническое уравнение прямой.

RM0394

TS_DATA – это как раз данные, которые получаем с АЦП. TS_CAL1 и TS_CAL2 – это показания АЦП на определенных температурах к которому был подсоединен температурный датчик. Найти эти показания АЦП и температуру предлагается в даташите на устройство, куда мы и переходим.

DS11451

Видим, что по определенным адресам лежат нужные нам данные АЦП при тепературах 30 и 130 град. Так же замечаем, что эти данные были записаны в память МК при определенном опопрном напряжении, равным 3 В.

АЦП производит относительные измерения – то есть сравнивает значение напряжения на входе с другим значением напряжения. В данном МК – относительно V_DDA или V_REF. Для правильного подсчета температуры, нужно измерить V_DDA и делать на него поправку. В моем случае – пин V_DDA совпадает с V_REF+ на МК. Если в вашем случае не так или не хочется заморачиваться – можно вручную измерить VDD_A или V_REF.

Calculating the actual VDDA (RM0394)

VREFINT_DATA – это значения АЦП для V_REFINT канала. Видим, что для расчета V_DDA нужен калибровочный коэффициент VREFINT_CAL, найти который предлагается снова в даташите на устройство, куда мы и переходим.

DS11451

Отлично, теперь мы знаем адрес VREFINT_CAL. Осталось сконфигурировать АЦП на снятие данных (V_REFINT) с внутреннего блока питания. V_REFINT в данном случае находится на 0 канале (ADC1_IN0).

V_REFINT channel block diagram (RM0394)

Теперь мы готовы написать код. Логика работы следующая:

  • Конфигурируем АЦП на снятие данных с V_REFINT
  • Находим V_DDA
  • Поправляем коэффициенты TS_CAL1 и TS_CAL2 под текущее напряжение
  • Конфигурируем АЦП на снятие данных с температурного датчика
  • Считаем температуру с новыми коэффициентами

#define TS_CAL1 *((uint16_t*)0x1FFF75A8)
#define TS_CAL2 *((uint16_t*)0x1FFF75CA)
#define VREFINT_CALIB *((uint16_t*)0x1FFF75AA)
#define TS_1 30.0f
#define TS_2 130.0f

float TS_CALIB1_NEW = 0.0f;
float TS_CALIB2_NEW = 0.0f;

void calc_vref(void){
    if (HAL_ADC_DeInit(&hadc1) != HAL_OK)
    {
        Error_Handler();
    }
    ADC_ChannelConfTypeDef sConfig = {0};
    hadc1.Instance = ADC1;
    hadc1.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV8;
    hadc1.Init.Resolution = ADC_RESOLUTION_12B;
    hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
    hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
    hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
    hadc1.Init.LowPowerAutoWait = DISABLE;
    hadc1.Init.ContinuousConvMode = DISABLE;
    hadc1.Init.NbrOfConversion = 1;
    hadc1.Init.DiscontinuousConvMode = DISABLE;
    hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
    hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
    hadc1.Init.DMAContinuousRequests = DISABLE;
    hadc1.Init.Overrun = ADC_OVR_DATA_PRESERVED;
    hadc1.Init.OversamplingMode = DISABLE;
    if (HAL_ADC_Init(&hadc1) != HAL_OK)
    {
        Error_Handler();
    }
    sConfig.Channel = ADC_CHANNEL_VREFINT;
    sConfig.Rank = ADC_REGULAR_RANK_1;
    sConfig.SamplingTime = ADC_SAMPLETIME_2CYCLES_5;
    sConfig.SingleDiff = ADC_SINGLE_ENDED;
    sConfig.OffsetNumber = ADC_OFFSET_NONE;
    sConfig.Offset = 0;
    if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
    {
        Error_Handler();
    }
    HAL_ADC_Start(&hadc1); //Запуск АЦП.
    if( HAL_ADC_PollForConversion( &hadc1, 10 ) == HAL_OK ) //Ожидание преобразования
    {
        float res = (float)HAL_ADC_GetValue(&hadc1);       //Чтение из АЦП
        float vdda_val = (3.0f*VREFINT_CAL)/(res);         //Находим значение Vdda
        float v_calib = 3.0f/vdda_val;                     //Находим соотношение
        TS_CALIB1_NEW  = TS_CAL1*v_calib;
        TS_CALIB2_NEW  = TS_CAL2*v_calib;
    }
    HAL_ADC_Stop(&hadc1); //Выкючаем АЦП
    if (HAL_ADC_DeInit(&hadc1) != HAL_OK)
    {
        Error_Handler();
    }
}

void get_stm_temp(float *temp)
{
    HAL_ADC_Start(&hadc1); // Запуск АЦП.
    if( HAL_ADC_PollForConversion( &hadc1, 20 ) == HAL_OK ) // Ожидание преобразования
    {
        float res = (float)HAL_ADC_GetValue(&hadc1);
        *temp = ((res - TS_CALIB1_NEW)*(TS_2 - TS_1))/(TS_CALIB2_NEW - TS_CALIB1_NEW) + TS_1;
    }
    HAL_ADC_Stop(&hadc1);
}

static void MX_ADC1_Init(void)
{
    ADC_ChannelConfTypeDef sConfig = {0};
    hadc1.Instance = ADC1;
    hadc1.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV8;
    hadc1.Init.Resolution = ADC_RESOLUTION_12B;
    hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
    hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
    hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
    hadc1.Init.LowPowerAutoWait = DISABLE;
    hadc1.Init.ContinuousConvMode = DISABLE;
    hadc1.Init.NbrOfConversion = 1;
    hadc1.Init.DiscontinuousConvMode = DISABLE;
    hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
    hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
    hadc1.Init.DMAContinuousRequests = DISABLE;
    hadc1.Init.Overrun = ADC_OVR_DATA_PRESERVED;
    hadc1.Init.OversamplingMode = DISABLE;
    if (HAL_ADC_Init(&hadc1) != HAL_OK)
    {
      Error_Handler();
    }
    sConfig.Channel = ADC_CHANNEL_TEMPSENSOR;
    sConfig.Rank = ADC_REGULAR_RANK_1;
    sConfig.SamplingTime = ADC_SAMPLETIME_2CYCLES_5;
    sConfig.SingleDiff = ADC_SINGLE_ENDED;
    sConfig.OffsetNumber = ADC_OFFSET_NONE;
    sConfig.Offset = 0;
    if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
    {
      Error_Handler();
    }
}

Comment

programel