Найти тему
ProgramEl

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

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

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

RM0394
RM0394

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

RM0394
RM0394

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

DS11451
DS11451

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

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

Calculating the actual VDDA (RM0394)
Calculating the actual VDDA (RM0394)

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

DS11451
DS11451

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

V_REFINT channel block diagram (RM0394)
V_REFINT channel block diagram (RM0394)

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

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

#define TS_CALIB1 *((uint16_t*)0x1FFF75A8)
#define TS_CALIB2 *((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();
}
}