diff --git a/APP/inc/ADC_Temp.h b/APP/inc/ADC_Temp.h index d1e29a5..5825892 100644 --- a/APP/inc/ADC_Temp.h +++ b/APP/inc/ADC_Temp.h @@ -8,6 +8,25 @@ #include #include "stdbool.h" +// ==================== НАСТРАИВАЕМЫЕ ПАРАМЕТРЫ ==================== +#define ADC_MAX 4095.0f // 12-битный АЦП (0-4095) + +// Используем целые числа для препроцессора (значения в милливольтах) +#define VREF_MV_INT 5000 // Опорное напряжение АЦП в мВ (5.0V) +#define VCC_DIVIDER_MV_INT 4960 // Напряжение питания делителя в мВ (4.96V) + +// Для использования в коде - преобразуем в float +#define VREF_MV ((float)VREF_MV_INT) +#define VCC_DIVIDER_MV ((float)VCC_DIVIDER_MV_INT) + +// Статическая проверка во время компиляции (работает с целыми числами) +#if VCC_DIVIDER_MV_INT == VREF_MV_INT +#define VCC_EQUALS_VREF 1 +#else +#define VCC_EQUALS_VREF 0 +#endif +// ================================================================= + // Типы таблиц NTC typedef enum { TABLE_DUCT = 0, // Таблица из документа KST45 (Duct) @@ -22,10 +41,6 @@ typedef enum { ALG_LINEAR = 2 } eAlg; -// Константы -#define ADC_MAX 4095.0f // 12-битный АЦП -#define VREF_MV 5000.0f // Опорное напряжение в милливольтах (5.0V) - // Параметры Steinhart-Hart для термистора (общие для всех таблиц) #define koef_A 0.001741624168166423 #define koef_B 0.00017003940268680147 @@ -42,7 +57,9 @@ typedef struct { uint16_t adc_value; // Значение АЦП int16_t temp_c; // Температура в °C * 10 (для фиксированной точки) float resistance_ohm; // Сопротивление в Ом, соответствующее значению АЦП - uint16_t voltage_mv; // Напряжение в милливольтах (ADC * VREF / ADC_MAX) + uint16_t voltage_mv; // Напряжение на NTC в милливольтах + float v_ntc_mv; // Точное напряжение на NTC + float v_r1_mv; // Напряжение на балластном резисторе eNtcTable table_type; // Тип таблицы, для которой вычислены данные } adc_temp_lookup; @@ -50,6 +67,8 @@ typedef struct { typedef struct { eNtcTable table_type; // Тип используемой таблицы float r1; // Сопротивление делителя напряжения (Ом) + float vcc_mv; // Напряжение питания делителя (мВ) + float vref_mv; // Опорное напряжение АЦП (мВ) int16_t start_temp; // Начальная температура таблицы (°C) int16_t end_temp; // Конечная температура таблицы (°C) uint16_t table_size; // Размер таблицы @@ -66,6 +85,8 @@ typedef struct { float duct_r1; float incar_r1; float ambient_r1; + float vcc_mv; // Общее Vcc для всех таблиц + float vref_mv; // Общее Vref для всех таблиц } fast_lookup_tables_t; // Глобальная структура для доступа к таблицам @@ -81,9 +102,19 @@ void set_active_config(eNtcTable table_type, float r1); const ntc_config_t* get_active_config(void); const fast_lookup_tables_t* get_fast_tables(void); -// Вспомогательная функция для преобразования ADC в напряжение +// Вспомогательные функции для преобразований static inline uint16_t adc_to_voltage_mv(uint16_t adc_value) { - return (uint16_t)(((uint32_t)adc_value * VREF_MV) / (uint32_t)ADC_MAX); + return (uint16_t)(((uint32_t)adc_value * (uint32_t)VREF_MV_INT) / (uint32_t)ADC_MAX); +} + +// Функция для получения напряжения на NTC с учетом VCC и VREF +static inline float get_ntc_voltage_mv(uint16_t adc_value) { + return (adc_value * VREF_MV) / ADC_MAX; +} + +// Функция для получения напряжения на балластном резисторе +static inline float get_r1_voltage_mv(uint16_t adc_value) { + return VCC_DIVIDER_MV - get_ntc_voltage_mv(adc_value); } // Основные функции получения температуры @@ -100,8 +131,16 @@ float get_resistance_log_fast(int16_t temperature_c10); float get_resistance_log_fast_for_table(int16_t temperature_c10, eNtcTable table_type); float get_resistance_fast_simple(int16_t temperature_c10); -// Новые функции для получения напряжения +// Новые функции для получения напряжений uint16_t get_voltage_from_adc(uint16_t adc_value); uint16_t get_voltage_fast_for_table(uint16_t adc_value, eNtcTable table_type); +float get_ntc_voltage_fast(uint16_t adc_value); +float get_r1_voltage_fast(uint16_t adc_value); + +// Функция для получения информации о конфигурации VCC/VREF +void get_vcc_vref_info(float* vcc_mv, float* vref_mv); +float get_vcc_divider_voltage(void); +float get_vref_voltage(void); +bool is_vcc_equal_to_vref(void); #endif //MDF_ADC_TEMP_KST45_14_2_H \ No newline at end of file diff --git a/APP/main.c b/APP/main.c index 54fd318..7dba1a3 100644 --- a/APP/main.c +++ b/APP/main.c @@ -1,32 +1,130 @@ #include #include "ADC_Temp.h" -// Функция для сохранения таблицы в файл -void save_table_to_file(const char* filename, const adc_temp_lookup* table, const char* table_name) { +// Упрощенная структура для хранения записи таблицы +typedef struct { + uint16_t adc_value; // Значение АЦП + int16_t temp_c; // Температура в °C * 10 + float resistance_ohm; // Сопротивление в Ом +} adc_temp_lookup_entry_t; + +// Функция для сохранения таблицы в C файл в виде статического массива +void save_table_to_c_file(const char* filename, const adc_temp_lookup* table, + const char* table_name, const char* array_name) { FILE* file = fopen(filename, "w"); if (file == NULL) { printf("Ошибка: не удалось создать файл %s\n", filename); return; } - fprintf(file, "=== Таблица быстрого поиска: %s ===\n", table_name); - fprintf(file, "Всего записей: %d\n", TABLE_SIZE_LOOKUP); - fprintf(file, "%-8s %-12s %-15s %-10s\n", "ADC", "Temp (°C)", "Resistance (Ω)", "Voltage (mV)"); - fprintf(file, "--------------------------------------------------------\n"); + // Заголовок файла + fprintf(file, "//\n"); + fprintf(file, "// Автоматически сгенерированный файл таблицы быстрого поиска: %s\n", table_name); + fprintf(file, "// VCC делителя = %.2f V\n", VCC_DIVIDER_MV / 1000.0f); + fprintf(file, "// VREF АЦП = %.2f V\n", VREF_MV / 1000.0f); + fprintf(file, "// Сопротивление R1 = %.0f Ом\n", + (table_name[0] == 'D' && table_name[1] == 'U') ? g_fast_tables.duct_r1 : + (table_name[0] == 'I') ? g_fast_tables.incar_r1 : g_fast_tables.ambient_r1); + fprintf(file, "// Всего записей: %d\n", TABLE_SIZE_LOOKUP); + fprintf(file, "//\n\n"); + fprintf(file, "#include \n\n"); + + // Структура для элемента таблицы + fprintf(file, "// Структура для хранения записи таблицы\n"); + fprintf(file, "typedef struct {\n"); + fprintf(file, " uint16_t adc_value; // Значение АЦП\n"); + fprintf(file, " int16_t temp_c; // Температура в °C * 10\n"); + fprintf(file, " float resistance_ohm; // Сопротивление в Ом\n"); + fprintf(file, "} adc_temp_lookup_entry_t;\n\n"); + + // Объявление массива + fprintf(file, "static const adc_temp_lookup_entry_t %s[%d] = {\n", + array_name, TABLE_SIZE_LOOKUP); + + // Вывод данных - по 4 элемента в строке для компактности for (int i = 0; i < TABLE_SIZE_LOOKUP; i++) { - fprintf(file, "%-8u %-12.1f %-15.2f %-10u\n", + if (i % 4 == 0) { + fprintf(file, " "); + } + + // Округляем сопротивление до 2 знаков после запятой + fprintf(file, "{%u, %d, %.2ff}", table[i].adc_value, - table[i].temp_c / 10.0f, - table[i].resistance_ohm, - table[i].voltage_mv); + table[i].temp_c, + table[i].resistance_ohm); + + if (i < TABLE_SIZE_LOOKUP - 1) { + fprintf(file, ", "); + } + + if ((i + 1) % 4 == 0) { + fprintf(file, "\n"); + } } + // Завершаем массив + if (TABLE_SIZE_LOOKUP % 4 != 0) { + fprintf(file, "\n"); + } + fprintf(file, "};\n\n"); + + // Добавляем функцию для быстрого доступа по ADC + fprintf(file, "// Быстрый поиск температуры по ADC (прямая индексация)\n"); + fprintf(file, "static inline int16_t %s_get_temp(uint16_t adc_value) {\n", array_name); + fprintf(file, " if (adc_value >= %d) return %s[%d].temp_c;\n", + TABLE_SIZE_LOOKUP, array_name, TABLE_SIZE_LOOKUP - 1); + fprintf(file, " return %s[adc_value].temp_c;\n", array_name); + fprintf(file, "}\n\n"); + + fprintf(file, "// Быстрый поиск сопротивления по ADC (прямая индексация)\n"); + fprintf(file, "static inline float %s_get_resistance(uint16_t adc_value) {\n", array_name); + fprintf(file, " if (adc_value >= %d) return %s[%d].resistance_ohm;\n", + TABLE_SIZE_LOOKUP, array_name, TABLE_SIZE_LOOKUP - 1); + fprintf(file, " return %s[adc_value].resistance_ohm;\n", array_name); + fprintf(file, "}\n\n"); + + // Добавляем функцию для обратного поиска (температура -> сопротивление) + fprintf(file, "// Поиск сопротивления по температуре (бинарный поиск)\n"); + fprintf(file, "static inline float %s_get_resistance_by_temp(int16_t temp_c10) {\n", array_name); + fprintf(file, " if (temp_c10 >= %s[0].temp_c) return %s[0].resistance_ohm;\n", array_name, array_name); + fprintf(file, " if (temp_c10 <= %s[%d].temp_c) return %s[%d].resistance_ohm;\n", + array_name, TABLE_SIZE_LOOKUP - 1, array_name, TABLE_SIZE_LOOKUP - 1); + fprintf(file, " \n"); + fprintf(file, " // Бинарный поиск\n"); + fprintf(file, " int left = 0, right = %d;\n", TABLE_SIZE_LOOKUP - 1); + fprintf(file, " while (left <= right) {\n"); + fprintf(file, " int mid = left + (right - left) / 2;\n"); + fprintf(file, " if (temp_c10 >= %s[mid].temp_c && temp_c10 <= %s[mid + 1].temp_c) {\n", array_name, array_name); + fprintf(file, " // Нашли интервал, интерполируем в логарифмическом масштабе\n"); + fprintf(file, " int16_t t_high = %s[mid].temp_c;\n", array_name); + fprintf(file, " int16_t t_low = %s[mid + 1].temp_c;\n", array_name); + fprintf(file, " float r_high = %s[mid].resistance_ohm;\n", array_name); + fprintf(file, " float r_low = %s[mid + 1].resistance_ohm;\n", array_name); + fprintf(file, " \n"); + fprintf(file, " if (t_high == t_low) return r_high;\n"); + fprintf(file, " \n"); + fprintf(file, " float log_r_high = logf(r_high);\n"); + fprintf(file, " float log_r_low = logf(r_low);\n"); + fprintf(file, " float log_r = log_r_high + (log_r_low - log_r_high) * \n"); + fprintf(file, " (float)(t_high - temp_c10) / (float)(t_high - t_low);\n"); + fprintf(file, " return expf(log_r);\n"); + fprintf(file, " }\n"); + fprintf(file, " \n"); + fprintf(file, " if (temp_c10 > %s[mid].temp_c) {\n", array_name); + fprintf(file, " right = mid - 1;\n"); + fprintf(file, " } else {\n"); + fprintf(file, " left = mid + 1;\n"); + fprintf(file, " }\n"); + fprintf(file, " }\n"); + fprintf(file, " return %s[%d].resistance_ohm;\n", array_name, TABLE_SIZE_LOOKUP - 1); + fprintf(file, "}\n"); + fclose(file); printf("Таблица '%s' сохранена в файл: %s\n", table_name, filename); } -// Функция для сохранения таблицы в CSV формате +// Функция для сохранения таблицы в CSV формате (для Excel) void save_table_to_csv(const char* filename, const adc_temp_lookup* table, const char* table_name) { FILE* file = fopen(filename, "w"); if (file == NULL) { @@ -35,83 +133,174 @@ void save_table_to_csv(const char* filename, const adc_temp_lookup* table, const } fprintf(file, "# Таблица быстрого поиска: %s\n", table_name); - fprintf(file, "ADC,Temperature_C,Resistance_Ohm,Voltage_mV\n"); + fprintf(file, "# VCC_DIVIDER = %.2f V, VREF = %.2f V\n", + VCC_DIVIDER_MV / 1000.0f, VREF_MV / 1000.0f); + fprintf(file, "ADC,Temperature_C,Temperature_x10,Resistance_Ohm\n"); for (int i = 0; i < TABLE_SIZE_LOOKUP; i++) { - fprintf(file, "%u,%.1f,%.2f,%u\n", + fprintf(file, "%u,%.1f,%d,%.2f\n", table[i].adc_value, table[i].temp_c / 10.0f, - table[i].resistance_ohm, - table[i].voltage_mv); + table[i].temp_c, + table[i].resistance_ohm); } fclose(file); printf("Таблица '%s' сохранена в CSV файл: %s\n", table_name, filename); } -// Функция для сохранения всех таблиц -void save_all_tables(void) { +// Функция для сохранения таблицы в TXT формате (для просмотра) +void save_table_to_txt(const char* filename, const adc_temp_lookup* table, const char* table_name) { + FILE* file = fopen(filename, "w"); + if (file == NULL) { + printf("Ошибка: не удалось создать файл %s\n", filename); + return; + } + + fprintf(file, "=== Таблица быстрого поиска: %s ===\n", table_name); + fprintf(file, "VCC делителя = %.2f V\n", VCC_DIVIDER_MV / 1000.0f); + fprintf(file, "VREF АЦП = %.2f V\n", VREF_MV / 1000.0f); + fprintf(file, "Всего записей: %d\n", TABLE_SIZE_LOOKUP); + fprintf(file, "%-8s %-12s %-15s\n", "ADC", "Temp (°C)", "Resistance (Ω)"); + fprintf(file, "---------------------------------------------\n"); + + // Выводим каждые 100 записей для компактности + for (int i = 0; i < TABLE_SIZE_LOOKUP; i++) { + fprintf(file, "%-8u %-12.1f %-15.2f\n", + table[i].adc_value, + table[i].temp_c / 10.0f, + table[i].resistance_ohm); + } + + fclose(file); + printf("Таблица '%s' сохранена в TXT файл: %s\n", table_name, filename); +} + +// Функция для сохранения всех таблиц во всех форматах +void save_all_tables_to_files(void) { const fast_lookup_tables_t* tables = get_fast_tables(); - // Сохраняем в текстовом формате - save_table_to_file("duct_table.txt", tables->duct, "DUCT"); - save_table_to_file("incar_table.txt", tables->incar, "INCAR"); - save_table_to_file("ambient_table.txt", tables->ambient, "AMBIENT"); + printf("\n=== Сохранение таблиц в файлы ===\n"); - // Сохраняем в CSV формате (удобно для Excel) + // Сохраняем DUCT таблицу + save_table_to_c_file("duct_table_array.c", tables->duct, "DUCT", "duct_lookup_table"); save_table_to_csv("duct_table.csv", tables->duct, "DUCT"); + save_table_to_txt("duct_table.txt", tables->duct, "DUCT"); + + // Сохраняем INCAR таблицу + save_table_to_c_file("incar_table_array.c", tables->incar, "INCAR", "incar_lookup_table"); save_table_to_csv("incar_table.csv", tables->incar, "INCAR"); + save_table_to_txt("incar_table.txt", tables->incar, "INCAR"); + + // Сохраняем AMBIENT таблицу + save_table_to_c_file("ambient_table_array.c", tables->ambient, "AMBIENT", "ambient_lookup_table"); save_table_to_csv("ambient_table.csv", tables->ambient, "AMBIENT"); + save_table_to_txt("ambient_table.txt", tables->ambient, "AMBIENT"); + + printf("\nВсе таблицы успешно сохранены!\n"); +} + +// Функция для проверки корректности таблицы +void validate_table(const adc_temp_lookup* table, const char* table_name, float r1) { + printf("\n=== Проверка таблицы: %s (R1=%.0fΩ) ===\n", table_name, r1); + + int monotonic_errors = 0; + int last_temp = table[0].temp_c; + float min_resistance = 999999999.0f; + float max_resistance = 0.0f; + + for (int i = 1; i < TABLE_SIZE_LOOKUP; i++) { + // Проверка монотонности температуры (должна убывать) + if (table[i].temp_c > last_temp) { + if (monotonic_errors < 5) { + printf("Ошибка монотонности: ADC=%u, Temp=%d > предыдущего %d\n", + table[i].adc_value, table[i].temp_c, last_temp); + } + monotonic_errors++; + } + last_temp = table[i].temp_c; + + // Поиск min/max сопротивления + if (table[i].resistance_ohm < min_resistance && table[i].resistance_ohm > 0) { + min_resistance = table[i].resistance_ohm; + } + if (table[i].resistance_ohm > max_resistance) { + max_resistance = table[i].resistance_ohm; + } + } + + if (monotonic_errors > 0) { + printf("Найдено %d ошибок монотонности\n", monotonic_errors); + } else { + printf("Монотонность: OK (температура монотонно убывает)\n"); + } + + printf("Диапазон сопротивлений: %.2f Ом ... %.2f Ом\n", max_resistance, min_resistance); + + // Вывод первых и последних записей + printf("\nПервые 10 записей:\n"); + for (int i = 0; i < 10 && i < TABLE_SIZE_LOOKUP; i++) { + printf(" ADC=%4u: Temp=%6.1f°C, R=%8.2fΩ\n", + table[i].adc_value, table[i].temp_c / 10.0f, table[i].resistance_ohm); + } + + printf("\nПоследние 10 записей:\n"); + for (int i = TABLE_SIZE_LOOKUP - 10; i < TABLE_SIZE_LOOKUP; i++) { + printf(" ADC=%4u: Temp=%6.1f°C, R=%8.2fΩ\n", + table[i].adc_value, table[i].temp_c / 10.0f, table[i].resistance_ohm); + } } int main() { - // Инициализируем все три таблицы с разными значениями R1 - init_all_tables(3000.0f, // R1 для DUCT (KST45) - 20000.0f, // R1 для INCAR - 20000.0f, // R1 для AMBIENT (NTC 10k) + printf("=== Генерация таблиц быстрого поиска для NTC термисторов ===\n"); + printf("Конфигурация:\n"); + printf(" VCC делителя = %.2f V\n", VCC_DIVIDER_MV / 1000.0f); + printf(" VREF АЦП = %.2f V\n", VREF_MV / 1000.0f); + printf(" ADC_MAX = %.0f\n", ADC_MAX); + printf(" Максимальное ADC при VCC=%.2fV: %.1f\n", + VCC_DIVIDER_MV / 1000.0f, (VCC_DIVIDER_MV * ADC_MAX) / VREF_MV); + + // Инициализируем все три таблицы + printf("\n=== Инициализация таблиц ===\n"); + init_all_tables(3000.0f, // R1 для DUCT (KST45) - 3kΩ + 20000.0f, // R1 для INCAR - 20kΩ + 20000.0f, // R1 для AMBIENT (NTC 10k) - 20kΩ ALG_STEINHART); - uint16_t adc_value = 2000; + printf("Таблицы успешно инициализированы\n"); - printf("\n=== Работа с тремя датчиками одновременно ===\n"); - - // Получаем температуру и напряжение для DUCT (KST45) - int16_t temp_duct = get_temperature_log_fast_for_table(adc_value, TABLE_DUCT); - uint16_t volt_duct = get_voltage_fast_for_table(adc_value, TABLE_DUCT); - printf("DUCT (KST45): ADC=%u, Temp=%.2f °C, Voltage=%u mV\n", - adc_value, temp_duct / 10.0f, volt_duct); - - // Получаем температуру и напряжение для INCAR - int16_t temp_incar = get_temperature_log_fast_for_table(adc_value, TABLE_INCAR); - uint16_t volt_incar = get_voltage_fast_for_table(adc_value, TABLE_INCAR); - printf("INCAR: ADC=%u, Temp=%.2f °C, Voltage=%u mV\n", - adc_value, temp_incar / 10.0f, volt_incar); - - // Получаем температуру и напряжение для AMBIENT (NTC 10k) - int16_t temp_ambient = get_temperature_log_fast_for_table(adc_value, TABLE_AMBIENT); - uint16_t volt_ambient = get_voltage_fast_for_table(adc_value, TABLE_AMBIENT); - printf("AMBIENT (NTC 10k): ADC=%u, Temp=%.2f °C, Voltage=%u mV\n", - adc_value, temp_ambient / 10.0f, volt_ambient); - - // Пример прямого вычисления напряжения - uint16_t direct_voltage = get_voltage_from_adc(adc_value); - printf("\nПрямое вычисление напряжения: ADC=%u -> %u mV\n", adc_value, direct_voltage); + // Проверяем таблицы + const fast_lookup_tables_t* tables = get_fast_tables(); + validate_table(tables->duct, "DUCT", g_fast_tables.duct_r1); + validate_table(tables->incar, "INCAR", g_fast_tables.incar_r1); + validate_table(tables->ambient, "AMBIENT", g_fast_tables.ambient_r1); // Сохраняем таблицы в файлы - printf("\n=== Сохранение таблиц в файлы ===\n"); - save_all_tables(); + save_all_tables_to_files(); - // Пример обратного преобразования (температура -> сопротивление) - printf("\n=== Обратное преобразование ===\n"); - int16_t temp_test = 250; // 25.0 °C - float resistance_duct = get_resistance_log_fast_for_table(temp_test, TABLE_DUCT); - float resistance_incar = get_resistance_log_fast_for_table(temp_test, TABLE_INCAR); - float resistance_ambient = get_resistance_log_fast_for_table(temp_test, TABLE_AMBIENT); + // Пример использования + printf("\n=== Пример использования ===\n"); + uint16_t test_adcs[] = {0, 500, 1000, 1500, 2000, 2500, 3000, 3500, 4000}; - printf("При %.1f °C:\n", temp_test / 10.0f); - printf(" DUCT сопротивление: %.2f Ω\n", resistance_duct); - printf(" INCAR сопротивление: %.2f Ω\n", resistance_incar); - printf(" AMBIENT сопротивление: %.2f Ω\n", resistance_ambient); + printf("ADC\tDUCT Temp\tINCAR Temp\tAMBIENT Temp\n"); + printf("------------------------------------------------\n"); + for (int i = 0; i < sizeof(test_adcs)/sizeof(test_adcs[0]); i++) { + uint16_t adc = test_adcs[i]; + int16_t temp_duct = get_temperature_log_fast_for_table(adc, TABLE_DUCT); + int16_t temp_incar = get_temperature_log_fast_for_table(adc, TABLE_INCAR); + int16_t temp_ambient = get_temperature_log_fast_for_table(adc, TABLE_AMBIENT); + + printf("%u\t%.1f°C\t\t%.1f°C\t\t%.1f°C\n", + adc, temp_duct / 10.0f, temp_incar / 10.0f, temp_ambient / 10.0f); + } + + printf("\n=== Готово ===\n"); + printf("Сгенерированы файлы:\n"); + printf(" - duct_table_array.c (статический массив)\n"); + printf(" - incar_table_array.c (статический массив)\n"); + printf(" - ambient_table_array.c (статический массив)\n"); + printf(" - duct_table.csv, incar_table.csv, ambient_table.csv (для Excel)\n"); + printf(" - duct_table.txt, incar_table.txt, ambient_table.txt (для просмотра)\n"); return 0; } \ No newline at end of file diff --git a/APP/src/ADC_Temp.c b/APP/src/ADC_Temp.c index c20f09f..c72b050 100644 --- a/APP/src/ADC_Temp.c +++ b/APP/src/ADC_Temp.c @@ -134,18 +134,23 @@ fast_lookup_tables_t g_fast_tables = { .ambient_initialized = false, .duct_r1 = 3300.0f, .incar_r1 = 3300.0f, - .ambient_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; @@ -208,15 +213,62 @@ static bool is_table_initialized(eNtcTable table_type) { } } -// Функция расчёта сопротивления NTC из значения АЦП -static float calculate_resistance(uint16_t adc_value, float r1) { +// Расширенная функция расчёта сопротивления 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; } - float R_ntc = r1 * (float) adc_value / (ADC_MAX - (float) adc_value); - return R_ntc; + 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; @@ -315,7 +367,7 @@ static float interpolate_steinhart_full(float resistance, int index, const ntc_t 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) { + if (resistance <= 0.0f || isinf(resistance)) { return -273.15f; // Ошибка } @@ -361,6 +413,16 @@ 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)) { @@ -399,7 +461,6 @@ uint16_t get_voltage_fast_for_table(uint16_t adc_value, eNtcTable table_type) { } } -// Инициализация таблицы быстрого поиска для конкретной таблицы void init_fast_lookup_table(eNtcTable table_type, float r1, eAlg use_alg) { uint16_t table_size; int16_t start_temp, end_temp; @@ -415,93 +476,103 @@ void init_fast_lookup_table(eNtcTable table_type, float r1, eAlg use_alg) { g_fast_tables.ambient_r1 = r1; } - // Находим рабочий диапазон АЦП + // Находим рабочий диапазон ADC uint16_t min_valid_adc = 0; - uint16_t max_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; // Минимальное сопротивление (макс. температура) - // Значения для граничных точек - 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 (максимальная температура) + // Ищем минимальное ADC, при котором сопротивление <= max_resistance 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) { + if (resistance <= max_resistance && !isinf(resistance)) { min_valid_adc = adc; - temp_at_min_adc = temp; - res_at_min_adc = resistance; break; } } - // Ищем максимальное АЦП, при котором температура <= end_temp (минимальная температура) + // Ищем максимальное ADC, при котором сопротивление >= min_resistance 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) { + if (resistance >= min_resistance && resistance < INFINITY) { max_valid_adc = adc; - temp_at_max_adc = temp; - res_at_max_adc = resistance; break; } } - // Заполняем таблицу для всего диапазона ADC от 0 до 4095 + 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; - 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); - } + 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 { - // В рабочем диапазоне - вычисляем нормально + // Зона низких 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); - temp = get_temperature_from_adc_with_table(adc, use_alg, table_type, 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].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; @@ -543,6 +614,8 @@ void init_all_tables(float r1_duct, float r1_incar, float r1_ambient, eAlg use_a 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); @@ -559,6 +632,24 @@ 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)) { @@ -629,7 +720,8 @@ float get_resistance_log_fast_for_table(int16_t temperature_c10, eNtcTable table while (left <= right) { int mid = left + (right - left) / 2; - if (temperature_c10 <= fast_table[mid].temp_c && + 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;