// // Created by cfif on 02.12.2025. // #include "ADC_Temp.h" #include #include #include // Структура для хранения табличных данных typedef struct { int temp_c; // Температура (°C) float r_nom; // Номинальное сопротивление (Ω) } ntc_table_entry; // Таблица из документа KST45 (Duct) static const ntc_table_entry ntc_table_duct[] = { {-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} }; // Таблица для NTC 10 кОм (Ambient) static const ntc_table_entry ntc_table_ambient[] = { {-40, 332776.0f}, {-35, 240264.0f}, {-30, 175427.0f}, {-25, 129449.0f}, {-20, 96481.0f}, {-15, 72592.0f}, {-10, 55109.0f}, {-5, 42193.0f}, {0, 32566.0f}, {5, 25338.0f}, {10, 19869.0f}, {15, 15695.0f}, {20, 12486.0f}, {25, 10000.0f}, {30, 8060.0f}, {35, 6536.0f}, {40, 5331.0f}, {45, 4373.0f}, {50, 3606.0f}, {55, 2990.0f}, {60, 2490.0f}, {65, 2085.0f}, {70, 1754.0f}, {75, 1482.0f}, {80, 1257.0f}, {85, 1071.0f}, {90, 916.4f}, {95, 786.9f}, {100, 678.1f}, {105, 586.5f}, {110, 509.1f}, {115, 443.3f}, {120, 387.3f}, {125, 339.5f}, {130, 298.4f}, {135, 263.1f}, {140, 232.6f}, {145, 206.1f}, {150, 183.2f} }; // Глобальная структура для хранения таблиц быстрого доступа fast_lookup_tables_t g_fast_tables = { .duct_initialized = false, .incar_initialized = false, .ambient_initialized = false, .duct_r1 = 3300.0f, .incar_r1 = 3300.0f, .ambient_r1 = 3300.0f, .vcc_mv = VCC_DIVIDER_MV, .vref_mv = VREF_MV }; // Активная конфигурация static ntc_config_t active_config = { .table_type = TABLE_DUCT, .r1 = 3300.0f, .vcc_mv = VCC_DIVIDER_MV, .vref_mv = VREF_MV, .start_temp = -40, .end_temp = 85, .table_size = TABLE_SIZE_DUCT }; // Вспомогательная функция для получения таблицы по типу 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_DUCT: table = ntc_table_duct; *size = TABLE_SIZE_DUCT; *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; case TABLE_AMBIENT: table = ntc_table_ambient; *size = TABLE_SIZE_AMBIENT; *start_temp = -40; *end_temp = 150; break; default: table = ntc_table_duct; *size = TABLE_SIZE_DUCT; *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_DUCT: return g_fast_tables.duct; case TABLE_INCAR: return g_fast_tables.incar; case TABLE_AMBIENT: return g_fast_tables.ambient; default: return g_fast_tables.duct; } } // Функция для получения статуса инициализации таблицы static bool is_table_initialized(eNtcTable table_type) { switch(table_type) { case TABLE_DUCT: return g_fast_tables.duct_initialized; case TABLE_INCAR: return g_fast_tables.incar_initialized; case TABLE_AMBIENT: return g_fast_tables.ambient_initialized; default: return false; } } // Расширенная функция расчёта сопротивления NTC с учетом VCC и VREF static float calculate_resistance_full(uint16_t adc_value, float r1, float vcc_mv, float vref_mv) { if (adc_value == 0) { return INFINITY; // Бесконечное сопротивление при 0 ADC (обрыв) } // Защита от переполнения if (adc_value >= (uint16_t) (ADC_MAX - 1)) { return 0.0f; // Нулевое сопротивление при максимальном ADC (короткое замыкание) } // Напряжение на NTC (измеренное АЦП) float v_ntc = (adc_value * vref_mv) / ADC_MAX; // Напряжение на балластном резисторе float v_r1 = vcc_mv - v_ntc; // Проверка на корректность if (v_r1 <= 0.0f) { return 0.0f; // Короткое замыкание или ошибка } if (v_ntc <= 0.0f) { return INFINITY; // Обрыв } // Сопротивление NTC (закон Ома) float r_ntc = r1 * (v_ntc / v_r1); // Ограничиваем разумными пределами (1 Ом - 10 МОм) if (r_ntc < 1.0f) r_ntc = 1.0f; if (r_ntc > 10000000.0f) r_ntc = 10000000.0f; return r_ntc; } // Оптимизированная функция для случая VCC == VREF static float calculate_resistance_optimized(uint16_t adc_value, float r1) { if (adc_value == 0 || adc_value >= (uint16_t) ADC_MAX) { return 0.0f; } return r1 * (float) adc_value / (ADC_MAX - (float) adc_value); } // Главная функция расчета сопротивления (использует константу времени компиляции) static float calculate_resistance(uint16_t adc_value, float r1) { #if VCC_EQUALS_VREF == 1 // Оптимизированный путь, когда напряжения равны return calculate_resistance_optimized(adc_value, r1); #else // Точный путь с учетом разных напряжений return calculate_resistance_full(adc_value, r1, VCC_DIVIDER_MV, VREF_MV); #endif } // Бинарный поиск в таблице 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 || isinf(resistance)) { 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); } // Функция для получения напряжения из АЦП uint16_t get_voltage_from_adc(uint16_t adc_value) { return adc_to_voltage_mv(adc_value); } // Функция для получения точного напряжения на NTC float get_ntc_voltage_fast(uint16_t adc_value) { return (adc_value * VREF_MV) / ADC_MAX; } // Функция для получения напряжения на балластном резисторе float get_r1_voltage_fast(uint16_t adc_value) { return VCC_DIVIDER_MV - ((adc_value * VREF_MV) / ADC_MAX); } // Функция для получения напряжения из таблицы быстрого доступа uint16_t get_voltage_fast_for_table(uint16_t adc_value, eNtcTable table_type) { if (!is_table_initialized(table_type)) { // Если таблица не инициализирована, вычисляем напрямую return adc_to_voltage_mv(adc_value); } adc_temp_lookup* fast_table = get_fast_table_by_type(table_type); // Поиск ближайшего значения в таблице for (uint16_t i = 0; i < TABLE_SIZE_LOOKUP; i++) { if (fast_table[i].adc_value == adc_value) { return fast_table[i].voltage_mv; } if (i < TABLE_SIZE_LOOKUP - 1 && 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; uint16_t v1 = fast_table[i].voltage_mv; uint16_t v2 = fast_table[i + 1].voltage_mv; if (adc2 == adc1) return v1; return v1 + (uint16_t)(((uint32_t)(v2 - v1) * (adc_value - adc1)) / (adc2 - adc1)); } } // Если не нашли, возвращаем ближайшее значение if (adc_value <= fast_table[0].adc_value) { return fast_table[0].voltage_mv; } else { return fast_table[TABLE_SIZE_LOOKUP - 1].voltage_mv; } } 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_DUCT) { g_fast_tables.duct_r1 = r1; } else if (table_type == TABLE_INCAR) { g_fast_tables.incar_r1 = r1; } else { g_fast_tables.ambient_r1 = r1; } // Находим рабочий диапазон ADC uint16_t min_valid_adc = 0; uint16_t max_valid_adc = (uint16_t)(ADC_MAX - 1); float max_resistance = table[0].r_nom; // Максимальное сопротивление (мин. температура) float min_resistance = table[table_size - 1].r_nom; // Минимальное сопротивление (макс. температура) // Ищем минимальное ADC, при котором сопротивление <= max_resistance for (uint16_t adc = 1; adc < ADC_MAX; adc++) { float resistance = calculate_resistance(adc, r1); if (resistance <= max_resistance && !isinf(resistance)) { min_valid_adc = adc; break; } } // Ищем максимальное ADC, при котором сопротивление >= min_resistance for (uint16_t adc = (uint16_t)(ADC_MAX - 1); adc > 0; adc--) { float resistance = calculate_resistance(adc, r1); if (resistance >= min_resistance && resistance < INFINITY) { max_valid_adc = adc; break; } } printf("DEBUG: %s - min_valid_adc=%u, max_valid_adc=%u\n", table_type == TABLE_DUCT ? "DUCT" : (table_type == TABLE_INCAR ? "INCAR" : "AMBIENT"), min_valid_adc, max_valid_adc); // Заполняем таблицу 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; float resistance; float temp; // Определяем ADC для текущей записи if (i == 0) { 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); } // Расчет сопротивления и температуры в зависимости от зоны if (adc <= min_valid_adc) { // Зона низких ADC (высокая температура) - насыщение resistance = min_resistance; temp = (float)end_temp; // Максимальная температура (+85°C) } else if (adc >= max_valid_adc) { // Зона высоких ADC (низкая температура) - насыщение resistance = max_resistance; temp = (float)start_temp; // Минимальная температура (-40°C) } else { // Рабочая зона - нормальный расчет resistance = calculate_resistance(adc, r1); if (isinf(resistance) || resistance <= 0.0f) { temp = (float)start_temp - 10.0f; } else { int index = find_interval_index(resistance, table, table_size); if (index < 0 || index >= table_size - 1) { if (resistance >= table[0].r_nom) { temp = (float)start_temp; } else if (resistance <= table[table_size - 1].r_nom) { temp = (float)end_temp; } else { temp = 25.0f; } } else { if (use_alg == ALG_STEINHART) { temp = interpolate_steinhart(resistance, index, table); } else if (use_alg == ALG_STEINHART_FULL) { temp = interpolate_steinhart_full(resistance, index, table); } else { temp = interpolate_log_linear(resistance, index, table); } } } } // Заполняем запись 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].voltage_mv = adc_to_voltage_mv(adc); fast_table[i].v_ntc_mv = (adc * VREF_MV) / ADC_MAX; fast_table[i].v_r1_mv = VCC_DIVIDER_MV - fast_table[i].v_ntc_mv; fast_table[i].table_type = table_type; prev_adc = adc; } // Устанавливаем флаг инициализации if (table_type == TABLE_DUCT) { g_fast_tables.duct_initialized = true; } else if (table_type == TABLE_INCAR) { g_fast_tables.incar_initialized = true; } else { g_fast_tables.ambient_initialized = true; } } // Инициализация таблицы Duct void init_duct_table(float r1, eAlg use_alg) { init_fast_lookup_table(TABLE_DUCT, r1, use_alg); } // Инициализация таблицы Incar void init_incar_table(float r1, eAlg use_alg) { init_fast_lookup_table(TABLE_INCAR, r1, use_alg); } // Инициализация таблицы Ambient void init_ambient_table(float r1, eAlg use_alg) { init_fast_lookup_table(TABLE_AMBIENT, r1, use_alg); } // Инициализация всех трех таблиц void init_all_tables(float r1_duct, float r1_incar, float r1_ambient, eAlg use_alg) { init_fast_lookup_table(TABLE_DUCT, r1_duct, use_alg); init_fast_lookup_table(TABLE_INCAR, r1_incar, use_alg); init_fast_lookup_table(TABLE_AMBIENT, r1_ambient, use_alg); } // Установка активной конфигурации void set_active_config(eNtcTable table_type, float r1) { active_config.table_type = table_type; active_config.r1 = r1; active_config.vcc_mv = VCC_DIVIDER_MV; active_config.vref_mv = VREF_MV; 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; } // Функция для получения информации о конфигурации VCC/VREF void get_vcc_vref_info(float* vcc_mv, float* vref_mv) { if (vcc_mv) *vcc_mv = VCC_DIVIDER_MV; if (vref_mv) *vref_mv = VREF_MV; } float get_vcc_divider_voltage(void) { return VCC_DIVIDER_MV; } float get_vref_voltage(void) { return VREF_MV; } bool is_vcc_equal_to_vref(void) { return (VCC_DIVIDER_MV_INT == VREF_MV_INT); } // Быстрая функция для конкретной таблицы int16_t get_temperature_log_fast_for_table(uint16_t adc_value, eNtcTable table_type) { if (!is_table_initialized(table_type)) { // Если таблица не инициализирована, используем обычный расчет float r1 = (table_type == TABLE_DUCT) ? g_fast_tables.duct_r1 : ((table_type == TABLE_INCAR) ? g_fast_tables.incar_r1 : g_fast_tables.ambient_r1); float temp = get_temperature_from_adc_with_table(adc_value, ALG_STEINHART, table_type, 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 (mid < TABLE_SIZE_LOOKUP - 1 && 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); }