HVAC_M7_ADC_TEMP/ADC_Temp.c

551 lines
20 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// Created by cfif on 02.12.2025.
//
#include "ADC_Temp.h"
#include <math.h>
#include <stdio.h>
#include <stdbool.h>
// Структура для хранения табличных данных
typedef struct {
int temp_c; // Температура (°C)
float r_nom; // Номинальное сопротивление (Ω)
} ntc_table_entry;
// Таблица из документа KST45
static const ntc_table_entry ntc_table_kst45[] = {
{-40, 100950.0f},
{-35, 72777.0f},
{-30, 53100.0f},
{-25, 39111.0f},
{-20, 29121.0f},
{-15, 21879.0f},
{-10, 16599.0f},
{-5, 12695.0f},
{0, 9795.0f},
{5, 7616.0f},
{10, 5970.0f},
{15, 4712.0f},
{20, 3747.0f},
{25, 3000.0f},
{30, 2417.0f},
{35, 1959.0f},
{40, 1598.0f},
{45, 1311.0f},
{50, 1081.0f},
{55, 895.9f},
{60, 746.4f},
{65, 624.9f},
{70, 525.6f},
{75, 444.4f},
{80, 377.4f},
{85, 321.7f}
};
// Таблица из документа Incar
static const ntc_table_entry ntc_table_incar[] = {
{-40, 101000.0f},
{-35, 72600.0f},
{-30, 52720.0f},
{-25, 38660.0f},
{-20, 28620.0f},
{-15, 21390.0f},
{-10, 16120.0f},
{-5, 12260.0f},
{-4, 11620.0f},
{-3, 11010.0f},
{-2, 10440.0f},
{-1, 9907.0f},
{0, 9399.0f},
{1, 8924.0f},
{2, 8473.0f},
{3, 8048.0f},
{4, 7646.0f},
{5, 7267.0f},
{6, 6909.0f},
{7, 6570.0f},
{8, 6250.0f},
{9, 5947.0f},
{10, 5661.0f},
{15, 4441.0f},
{20, 3512.0f},
{25, 2795.0f},
{30, 2239.0f},
{35, 1806.0f},
{40, 1464.0f},
{45, 1195.0f},
{50, 980.0f},
{55, 809.0f},
{60, 670.0f},
{65, 559.0f},
{70, 468.0f},
{75, 394.0f},
{80, 333.0f},
{85, 283.0f}
};
// Глобальная структура для хранения таблиц быстрого доступа
fast_lookup_tables_t g_fast_tables = {
.kst45_initialized = false,
.incar_initialized = false,
.kst45_r1 = 3300.0f,
.incar_r1 = 3300.0f
};
// Активная конфигурация
static ntc_config_t active_config = {
.table_type = TABLE_KST45,
.r1 = 3300.0f,
.start_temp = -40,
.end_temp = 85,
.table_size = TABLE_SIZE_KST45
};
// Вспомогательная функция для получения таблицы по типу
static const ntc_table_entry* get_table_by_type(eNtcTable table_type, uint16_t* size, int16_t* start_temp, int16_t* end_temp) {
const ntc_table_entry* table = NULL;
*size = 0;
switch(table_type) {
case TABLE_KST45:
table = ntc_table_kst45;
*size = TABLE_SIZE_KST45;
*start_temp = -40;
*end_temp = 85;
break;
case TABLE_INCAR:
table = ntc_table_incar;
*size = TABLE_SIZE_INCAR;
*start_temp = -40;
*end_temp = 85;
break;
default:
table = ntc_table_kst45;
*size = TABLE_SIZE_KST45;
*start_temp = -40;
*end_temp = 85;
break;
}
return table;
}
// Функция для получения указателя на таблицу быстрого доступа по типу
static adc_temp_lookup* get_fast_table_by_type(eNtcTable table_type) {
switch(table_type) {
case TABLE_KST45:
return g_fast_tables.kst45;
case TABLE_INCAR:
return g_fast_tables.incar;
default:
return g_fast_tables.kst45;
}
}
// Функция для получения статуса инициализации таблицы
static bool is_table_initialized(eNtcTable table_type) {
switch(table_type) {
case TABLE_KST45:
return g_fast_tables.kst45_initialized;
case TABLE_INCAR:
return g_fast_tables.incar_initialized;
default:
return false;
}
}
// Функция расчёта сопротивления NTC из значения АЦП
static float calculate_resistance(uint16_t adc_value, float r1) {
if (adc_value == 0 || adc_value >= (uint16_t) ADC_MAX) {
return 0.0f;
}
float R_ntc = r1 * (float) adc_value / (ADC_MAX - (float) adc_value);
return R_ntc;
}
// Бинарный поиск в таблице
static int find_interval_index(float resistance, const ntc_table_entry* table, uint16_t table_size) {
int left = 0;
int right = table_size - 1;
// Проверка границ
if (resistance >= table[0].r_nom) return 0;
if (resistance <= table[right].r_nom) return right - 1;
// Бинарный поиск
while (left <= right) {
int mid = left + (right - left) / 2;
if (mid < table_size - 1 &&
resistance <= table[mid].r_nom &&
resistance >= table[mid + 1].r_nom) {
return mid;
}
if (resistance > table[mid].r_nom) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return -1; // Не найден
}
// Интерполяция с использованием уравнения Стейнхарта-Харта между точками
static float interpolate_steinhart(float resistance, int index, const ntc_table_entry* table) {
// Берем две соседние точки из таблицы
float t1 = (float) table[index].temp_c + 273.15f; // в Кельвинах
float t2 = (float) table[index + 1].temp_c + 273.15f;
float r1 = table[index].r_nom;
float r2 = table[index + 1].r_nom;
// Вычисляем коэффициент B для интервала
float B = logf(r1 / r2) / (1.0f / t1 - 1.0f / t2);
// Используем уравнение Стейнхарта-Харта для вычисления температуры
float steinhart = logf(resistance / r1) / B + 1.0f / t1;
float temp_k = 1.0f / steinhart;
return temp_k - 273.15f; // Конвертация в °C
}
// Линейная интерполяция в логарифмическом масштабе
static float interpolate_log_linear(float resistance, int index, const ntc_table_entry* table) {
float t1 = (float) table[index].temp_c;
float t2 = (float) table[index + 1].temp_c;
float r1 = table[index].r_nom;
float r2 = table[index + 1].r_nom;
float log_r1 = logf(r1);
float log_r2 = logf(r2);
float log_r = logf(resistance);
return t1 + (t2 - t1) * (log_r - log_r1) / (log_r2 - log_r1);
}
// Более надежная версия с проверкой параметров
static float interpolate_steinhart_full(float resistance, int index, const ntc_table_entry* table) {
// Проверка корректности входных данных
if (resistance <= 0.0f) {
return -273.15f; // Абсолютный ноль при некорректном сопротивлении
}
// Для повышения точности можно использовать таблицу как справочную,
// но основное вычисление - по уравнению с коэффициентами
double L = logf(resistance);
// Для термисторов NTC коэффициент C обычно очень маленький (порядка 1e-7...1e-8)
// Убедимся, что кубический член вычислен корректно
double L3 = L * L * L;
double inv_T = koef_A + koef_B * L + koef_C * L3;
// Проверка на физическую реализуемость (температура должна быть положительной в Кельвинах)
if (inv_T <= 0.0f || inv_T > 1.0f) { // 1/T не может быть <= 0 (T < 0K) или слишком большим
// В случае ошибки возвращаем температуру из таблицы по индексу
return (float) table[index].temp_c;
}
double temp_K = 1.0f / inv_T;
// Дополнительная проверка диапазона (например, для NTC обычно -50...+150°C)
if (temp_K < 223.15f || temp_K > 423.15f) { // -50°C...150°C в Кельвинах
// Возвращаем значение из таблицы как запасной вариант
return (float) table[index].temp_c;
}
return (float) (temp_K - 273.15f);
}
// Основная функция для получения температуры с указанием таблицы и R1
float get_temperature_from_adc_with_table(uint16_t adc_value, eAlg alg, eNtcTable table_type, float r1) {
float resistance = calculate_resistance(adc_value, r1);
if (resistance <= 0.0f) {
return -273.15f; // Ошибка
}
uint16_t table_size;
int16_t start_temp, end_temp;
const ntc_table_entry* table = get_table_by_type(table_type, &table_size, &start_temp, &end_temp);
int index = find_interval_index(resistance, table, table_size);
if (index < 0 || index >= table_size - 1) {
// Вне диапазона таблицы
if (resistance >= table[0].r_nom) return (float) start_temp;
if (resistance <= table[table_size - 1].r_nom) return (float) end_temp;
return -273.15f; // Ошибка
}
if (alg == ALG_STEINHART) {
return interpolate_steinhart(resistance, index, table);
} else if (alg == ALG_STEINHART_FULL) {
return interpolate_steinhart_full(resistance, index, table);
} else {
return interpolate_log_linear(resistance, index, table);
}
}
// Функция для получения температуры с активной конфигурацией
float get_temperature_from_adc(uint16_t adc_value, eAlg alg) {
return get_temperature_from_adc_with_table(adc_value, alg, active_config.table_type, active_config.r1);
}
// Функция для получения сопротивления из значения АЦП с указанием таблицы
float get_resistance_from_adc_with_table(uint16_t adc_value, eNtcTable table_type, float r1) {
return calculate_resistance(adc_value, r1);
}
// Функция для получения сопротивления с активной конфигурацией
float get_resistance_from_adc(uint16_t adc_value) {
return calculate_resistance(adc_value, active_config.r1);
}
// Инициализация таблицы быстрого поиска для конкретной таблицы
void init_fast_lookup_table(eNtcTable table_type, float r1, eAlg use_alg) {
uint16_t table_size;
int16_t start_temp, end_temp;
const ntc_table_entry* table = get_table_by_type(table_type, &table_size, &start_temp, &end_temp);
adc_temp_lookup* fast_table = get_fast_table_by_type(table_type);
// Сохраняем значение R1 для этой таблицы
if (table_type == TABLE_KST45) {
g_fast_tables.kst45_r1 = r1;
} else {
g_fast_tables.incar_r1 = r1;
}
// Находим рабочий диапазон АЦП
uint16_t min_valid_adc = 0;
uint16_t max_valid_adc = 0;
// Значения для граничных точек
float temp_at_min_adc = 0.0f;
float temp_at_max_adc = 0.0f;
float res_at_min_adc = 0.0f;
float res_at_max_adc = 0.0f;
// Ищем минимальное АЦП, при котором температура >= start_temp (максимальная температура)
for (uint16_t adc = 1; adc < ADC_MAX; adc++) {
float resistance = calculate_resistance(adc, r1);
float temp = get_temperature_from_adc_with_table(adc, use_alg, table_type, r1);
if (temp >= start_temp && temp <= end_temp) {
min_valid_adc = adc;
temp_at_min_adc = temp;
res_at_min_adc = resistance;
break;
}
}
// Ищем максимальное АЦП, при котором температура <= end_temp (минимальная температура)
for (uint16_t adc = (uint16_t)(ADC_MAX - 1); adc > 0; adc--) {
float resistance = calculate_resistance(adc, r1);
float temp = get_temperature_from_adc_with_table(adc, use_alg, table_type, r1);
if (temp >= start_temp && temp <= end_temp) {
max_valid_adc = adc;
temp_at_max_adc = temp;
res_at_max_adc = resistance;
break;
}
}
// Заполняем таблицу для всего диапазона ADC от 0 до 4095
uint32_t adc_step = ((uint32_t)(ADC_MAX - 1) * 1000) / (TABLE_SIZE_LOOKUP - 1);
uint32_t current_adc = 0;
uint16_t prev_adc = 0;
for (uint16_t i = 0; i < TABLE_SIZE_LOOKUP; i++) {
uint16_t adc;
if (i == 0) {
adc = 0;
current_adc = 0;
prev_adc = 0;
} else if (i == TABLE_SIZE_LOOKUP - 1) {
adc = (uint16_t)(ADC_MAX - 1);
} else {
current_adc += adc_step;
adc = (uint16_t)(current_adc / 1000);
// Убеждаемся, что значения монотонно возрастают и нет дубликатов
if (adc <= prev_adc) {
adc = prev_adc + 1;
}
// Убеждаемся, что не выходим за пределы
if (adc >= (uint16_t)(ADC_MAX - 1)) {
adc = (uint16_t)(ADC_MAX - 2);
}
}
float resistance;
float temp;
// Определяем, находится ли ADC в рабочем диапазоне
if (adc <= min_valid_adc) {
// Зона низких ADC (высокая температура) - используем граничное значение
resistance = res_at_min_adc;
temp = temp_at_min_adc;
} else if (adc >= max_valid_adc) {
// Зона высоких ADC (низкая температура) - используем граничное значение
resistance = res_at_max_adc;
temp = temp_at_max_adc;
} else {
// В рабочем диапазоне - вычисляем нормально
resistance = calculate_resistance(adc, r1);
temp = get_temperature_from_adc_with_table(adc, use_alg, table_type, r1);
}
fast_table[i].adc_value = adc;
fast_table[i].temp_c = (int16_t)(temp * 10.0f);
fast_table[i].resistance_ohm = resistance;
fast_table[i].table_type = table_type;
prev_adc = adc;
}
// Устанавливаем флаг инициализации
if (table_type == TABLE_KST45) {
g_fast_tables.kst45_initialized = true;
} else {
g_fast_tables.incar_initialized = true;
}
}
// Инициализация обеих таблиц
void init_both_tables(float r1_kst45, float r1_incar, eAlg use_alg) {
init_fast_lookup_table(TABLE_KST45, r1_kst45, use_alg);
init_fast_lookup_table(TABLE_INCAR, r1_incar, use_alg);
}
// Установка активной конфигурации
void set_active_config(eNtcTable table_type, float r1) {
active_config.table_type = table_type;
active_config.r1 = r1;
uint16_t table_size;
get_table_by_type(table_type, &table_size, &active_config.start_temp, &active_config.end_temp);
active_config.table_size = table_size;
}
// Получение активной конфигурации
const ntc_config_t* get_active_config(void) {
return &active_config;
}
// Получение таблиц быстрого доступа
const fast_lookup_tables_t* get_fast_tables(void) {
return &g_fast_tables;
}
// Быстрая функция для конкретной таблицы
int16_t get_temperature_log_fast_for_table(uint16_t adc_value, eNtcTable table_type) {
if (!is_table_initialized(table_type)) {
// Если таблица не инициализирована, используем обычный расчет
float temp = get_temperature_from_adc_with_table(adc_value, ALG_STEINHART, table_type,
(table_type == TABLE_KST45) ? g_fast_tables.kst45_r1 : g_fast_tables.incar_r1);
return (int16_t)(temp * 10.0f);
}
adc_temp_lookup* fast_table = get_fast_table_by_type(table_type);
// Простой линейный поиск (так как таблица небольшая - 4096 элементов)
for (uint16_t i = 0; i < TABLE_SIZE_LOOKUP - 1; i++) {
if (adc_value >= fast_table[i].adc_value && adc_value <= fast_table[i + 1].adc_value) {
// Линейная интерполяция
uint16_t adc1 = fast_table[i].adc_value;
uint16_t adc2 = fast_table[i + 1].adc_value;
int16_t temp1 = fast_table[i].temp_c;
int16_t temp2 = fast_table[i + 1].temp_c;
if (adc2 == adc1) {
return temp1;
}
return temp1 + ((int32_t)(temp2 - temp1) * (adc_value - adc1)) / (adc2 - adc1);
}
}
// Если не нашли, возвращаем ближайшее значение
if (adc_value <= fast_table[0].adc_value) {
return fast_table[0].temp_c;
} else {
return fast_table[TABLE_SIZE_LOOKUP - 1].temp_c;
}
}
// Быстрые функции с использованием активной таблицы
int16_t get_temperature_log_fast(uint16_t adc_value) {
return get_temperature_log_fast_for_table(adc_value, active_config.table_type);
}
// Альтернативная простая версия
int16_t get_temperature_linear_fast(uint16_t adc_value) {
return get_temperature_log_fast_for_table(adc_value, active_config.table_type);
}
// Функция для получения сопротивления из температуры для конкретной таблицы
float get_resistance_log_fast_for_table(int16_t temperature_c10, eNtcTable table_type) {
if (!is_table_initialized(table_type)) {
return 0.0f;
}
adc_temp_lookup* fast_table = get_fast_table_by_type(table_type);
// Защита от выхода за границы
if (temperature_c10 >= fast_table[0].temp_c) {
return fast_table[0].resistance_ohm;
}
if (temperature_c10 <= fast_table[TABLE_SIZE_LOOKUP - 1].temp_c) {
return fast_table[TABLE_SIZE_LOOKUP - 1].resistance_ohm;
}
// Бинарный поиск интервала по температуре
int left = 0;
int right = TABLE_SIZE_LOOKUP - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (temperature_c10 <= fast_table[mid].temp_c &&
temperature_c10 >= fast_table[mid + 1].temp_c) {
// Нашли интервал
int16_t temp_high = fast_table[mid].temp_c;
int16_t temp_low = fast_table[mid + 1].temp_c;
float res_high = fast_table[mid].resistance_ohm;
float res_low = fast_table[mid + 1].resistance_ohm;
if (temp_high == temp_low) {
return res_high;
}
// Интерполяция в логарифмическом масштабе
float log_res_high = logf(res_high);
float log_res_low = logf(res_low);
float log_res = log_res_high + (log_res_low - log_res_high) *
(float)(temp_high - temperature_c10) / (float)(temp_high - temp_low);
return expf(log_res);
}
if (temperature_c10 > fast_table[mid].temp_c) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return fast_table[TABLE_SIZE_LOOKUP - 1].resistance_ohm;
}
// Функция для получения сопротивления из температуры (обратное преобразование)
float get_resistance_log_fast(int16_t temperature_c10) {
return get_resistance_log_fast_for_table(temperature_c10, active_config.table_type);
}
// Упрощенная версия с линейным поиском
float get_resistance_fast_simple(int16_t temperature_c10) {
return get_resistance_log_fast_for_table(temperature_c10, active_config.table_type);
}