Обычная затея – считать температуру контроллера может заиграть новыми красками, когда ты внезапно получишь отрицательные/ниже комнатных значения. Что-то явно пошло не так – и это нужно исправлять. Давайте получим нормальную температуру с внутреннего датчика микроконтроллера STM32 (STM32L432 в нашем случае).
Откроем референс мануал для данного семейства RM0394 на секции с температурным датчиком.
Блок-схема нам говорит, что к 17 каналу АЦП подключен температурный датчик, который выдает V_TS – напряжение, которое линейно (почти) зависит от температуры. Дальше нужно перевести получаемые отсчеты АЦП в градусы. Чуть ниже нам приводят формулу для рассчета температуры в градусах Цельсия. Это всего лишь каноническое уравнение прямой.
TS_DATA – это как раз данные, которые получаем с АЦП. TS_CAL1 и TS_CAL2 – это показания АЦП на определенных температурах к которому был подсоединен температурный датчик. Найти эти показания АЦП и температуру предлагается в даташите на устройство, куда мы и переходим.
Видим, что по определенным адресам лежат нужные нам данные АЦП при тепературах 30 и 130 град. Так же замечаем, что эти данные были записаны в память МК при определенном опопрном напряжении, равным 3 В.
АЦП производит относительные измерения – то есть сравнивает значение напряжения на входе с другим значением напряжения. В данном МК – относительно V_DDA или V_REF. Для правильного подсчета температуры, нужно измерить V_DDA и делать на него поправку. В моем случае – пин V_DDA совпадает с V_REF+ на МК. Если в вашем случае не так или не хочется заморачиваться – можно вручную измерить VDD_A или V_REF.
VREFINT_DATA – это значения АЦП для V_REFINT канала. Видим, что для расчета V_DDA нужен калибровочный коэффициент VREFINT_CAL, найти который предлагается снова в даташите на устройство, куда мы и переходим.
Отлично, теперь мы знаем адрес VREFINT_CAL. Осталось сконфигурировать АЦП на снятие данных (V_REFINT) с внутреннего блока питания. V_REFINT в данном случае находится на 0 канале (ADC1_IN0).
Теперь мы готовы написать код. Логика работы следующая:
- Конфигурируем АЦП на снятие данных с 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