This commit is contained in:
cfif 2026-05-12 17:16:47 +03:00
parent cbd0e62ca2
commit 1075ecf4ff
3 changed files with 446 additions and 126 deletions

View File

@ -8,6 +8,25 @@
#include <stdint.h> #include <stdint.h>
#include "stdbool.h" #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 // Типы таблиц NTC
typedef enum { typedef enum {
TABLE_DUCT = 0, // Таблица из документа KST45 (Duct) TABLE_DUCT = 0, // Таблица из документа KST45 (Duct)
@ -22,10 +41,6 @@ typedef enum {
ALG_LINEAR = 2 ALG_LINEAR = 2
} eAlg; } eAlg;
// Константы
#define ADC_MAX 4095.0f // 12-битный АЦП
#define VREF_MV 5000.0f // Опорное напряжение в милливольтах (5.0V)
// Параметры Steinhart-Hart для термистора (общие для всех таблиц) // Параметры Steinhart-Hart для термистора (общие для всех таблиц)
#define koef_A 0.001741624168166423 #define koef_A 0.001741624168166423
#define koef_B 0.00017003940268680147 #define koef_B 0.00017003940268680147
@ -42,7 +57,9 @@ typedef struct {
uint16_t adc_value; // Значение АЦП uint16_t adc_value; // Значение АЦП
int16_t temp_c; // Температура в °C * 10 (для фиксированной точки) int16_t temp_c; // Температура в °C * 10 (для фиксированной точки)
float resistance_ohm; // Сопротивление в Ом, соответствующее значению АЦП 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; // Тип таблицы, для которой вычислены данные eNtcTable table_type; // Тип таблицы, для которой вычислены данные
} adc_temp_lookup; } adc_temp_lookup;
@ -50,6 +67,8 @@ typedef struct {
typedef struct { typedef struct {
eNtcTable table_type; // Тип используемой таблицы eNtcTable table_type; // Тип используемой таблицы
float r1; // Сопротивление делителя напряжения (Ом) float r1; // Сопротивление делителя напряжения (Ом)
float vcc_mv; // Напряжение питания делителя (мВ)
float vref_mv; // Опорное напряжение АЦП (мВ)
int16_t start_temp; // Начальная температура таблицы (°C) int16_t start_temp; // Начальная температура таблицы (°C)
int16_t end_temp; // Конечная температура таблицы (°C) int16_t end_temp; // Конечная температура таблицы (°C)
uint16_t table_size; // Размер таблицы uint16_t table_size; // Размер таблицы
@ -66,6 +85,8 @@ typedef struct {
float duct_r1; float duct_r1;
float incar_r1; float incar_r1;
float ambient_r1; float ambient_r1;
float vcc_mv; // Общее Vcc для всех таблиц
float vref_mv; // Общее Vref для всех таблиц
} fast_lookup_tables_t; } 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 ntc_config_t* get_active_config(void);
const fast_lookup_tables_t* get_fast_tables(void); const fast_lookup_tables_t* get_fast_tables(void);
// Вспомогательная функция для преобразования ADC в напряжение // Вспомогательные функции для преобразований
static inline uint16_t adc_to_voltage_mv(uint16_t adc_value) { 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_log_fast_for_table(int16_t temperature_c10, eNtcTable table_type);
float get_resistance_fast_simple(int16_t temperature_c10); float get_resistance_fast_simple(int16_t temperature_c10);
// Новые функции для получения напряжения // Новые функции для получения напряжений
uint16_t get_voltage_from_adc(uint16_t adc_value); uint16_t get_voltage_from_adc(uint16_t adc_value);
uint16_t get_voltage_fast_for_table(uint16_t adc_value, eNtcTable table_type); 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 #endif //MDF_ADC_TEMP_KST45_14_2_H

View File

@ -1,32 +1,130 @@
#include <stdio.h> #include <stdio.h>
#include "ADC_Temp.h" #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"); FILE* file = fopen(filename, "w");
if (file == NULL) { if (file == NULL) {
printf("Ошибка: не удалось создать файл %s\n", filename); printf("Ошибка: не удалось создать файл %s\n", filename);
return; return;
} }
fprintf(file, "=== Таблица быстрого поиска: %s ===\n", table_name); // Заголовок файла
fprintf(file, "Всего записей: %d\n", TABLE_SIZE_LOOKUP); fprintf(file, "//\n");
fprintf(file, "%-8s %-12s %-15s %-10s\n", "ADC", "Temp (°C)", "Resistance (Ω)", "Voltage (mV)"); fprintf(file, "// Автоматически сгенерированный файл таблицы быстрого поиска: %s\n", table_name);
fprintf(file, "--------------------------------------------------------\n"); 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 <stdint.h>\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++) { 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].adc_value,
table[i].temp_c / 10.0f, table[i].temp_c,
table[i].resistance_ohm, table[i].resistance_ohm);
table[i].voltage_mv);
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); fclose(file);
printf("Таблица '%s' сохранена в файл: %s\n", table_name, filename); 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) { void save_table_to_csv(const char* filename, const adc_temp_lookup* table, const char* table_name) {
FILE* file = fopen(filename, "w"); FILE* file = fopen(filename, "w");
if (file == NULL) { 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, "# Таблица быстрого поиска: %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++) { 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].adc_value,
table[i].temp_c / 10.0f, table[i].temp_c / 10.0f,
table[i].resistance_ohm, table[i].temp_c,
table[i].voltage_mv); table[i].resistance_ohm);
} }
fclose(file); fclose(file);
printf("Таблица '%s' сохранена в CSV файл: %s\n", table_name, filename); printf("Таблица '%s' сохранена в CSV файл: %s\n", table_name, filename);
} }
// Функция для сохранения всех таблиц // Функция для сохранения таблицы в TXT формате (для просмотра)
void save_all_tables(void) { 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(); const fast_lookup_tables_t* tables = get_fast_tables();
// Сохраняем в текстовом формате printf("\n=== Сохранение таблиц в файлы ===\n");
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");
// Сохраняем в 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_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_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_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() { int main() {
// Инициализируем все три таблицы с разными значениями R1 printf("=== Генерация таблиц быстрого поиска для NTC термисторов ===\n");
init_all_tables(3000.0f, // R1 для DUCT (KST45) printf("Конфигурация:\n");
20000.0f, // R1 для INCAR printf(" VCC делителя = %.2f V\n", VCC_DIVIDER_MV / 1000.0f);
20000.0f, // R1 для AMBIENT (NTC 10k) 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); ALG_STEINHART);
uint16_t adc_value = 2000; printf("Таблицы успешно инициализированы\n");
printf("\n=== Работа с тремя датчиками одновременно ===\n"); // Проверяем таблицы
const fast_lookup_tables_t* tables = get_fast_tables();
// Получаем температуру и напряжение для DUCT (KST45) validate_table(tables->duct, "DUCT", g_fast_tables.duct_r1);
int16_t temp_duct = get_temperature_log_fast_for_table(adc_value, TABLE_DUCT); validate_table(tables->incar, "INCAR", g_fast_tables.incar_r1);
uint16_t volt_duct = get_voltage_fast_for_table(adc_value, TABLE_DUCT); validate_table(tables->ambient, "AMBIENT", g_fast_tables.ambient_r1);
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);
// Сохраняем таблицы в файлы // Сохраняем таблицы в файлы
printf("\n=== Сохранение таблиц в файлы ===\n"); save_all_tables_to_files();
save_all_tables();
// Пример обратного преобразования (температура -> сопротивление) // Пример использования
printf("\n=== Обратное преобразование ===\n"); printf("\n=== Пример использования ===\n");
int16_t temp_test = 250; // 25.0 °C uint16_t test_adcs[] = {0, 500, 1000, 1500, 2000, 2500, 3000, 3500, 4000};
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("При %.1f °C:\n", temp_test / 10.0f); printf("ADC\tDUCT Temp\tINCAR Temp\tAMBIENT Temp\n");
printf(" DUCT сопротивление: %.2f Ω\n", resistance_duct); printf("------------------------------------------------\n");
printf(" INCAR сопротивление: %.2f Ω\n", resistance_incar); for (int i = 0; i < sizeof(test_adcs)/sizeof(test_adcs[0]); i++) {
printf(" AMBIENT сопротивление: %.2f Ω\n", resistance_ambient); 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; return 0;
} }

View File

@ -134,18 +134,23 @@ fast_lookup_tables_t g_fast_tables = {
.ambient_initialized = false, .ambient_initialized = false,
.duct_r1 = 3300.0f, .duct_r1 = 3300.0f,
.incar_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 = { static ntc_config_t active_config = {
.table_type = TABLE_DUCT, .table_type = TABLE_DUCT,
.r1 = 3300.0f, .r1 = 3300.0f,
.vcc_mv = VCC_DIVIDER_MV,
.vref_mv = VREF_MV,
.start_temp = -40, .start_temp = -40,
.end_temp = 85, .end_temp = 85,
.table_size = TABLE_SIZE_DUCT .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) { 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; const ntc_table_entry* table = NULL;
@ -208,15 +213,62 @@ static bool is_table_initialized(eNtcTable table_type) {
} }
} }
// Функция расчёта сопротивления NTC из значения АЦП // Расширенная функция расчёта сопротивления NTC с учетом VCC и VREF
static float calculate_resistance(uint16_t adc_value, float r1) { 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) { if (adc_value == 0 || adc_value >= (uint16_t) ADC_MAX) {
return 0.0f; return 0.0f;
} }
float R_ntc = r1 * (float) adc_value / (ADC_MAX - (float) adc_value); return r1 * (float) adc_value / (ADC_MAX - (float) adc_value);
return R_ntc;
} }
// Главная функция расчета сопротивления (использует константу времени компиляции)
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) { static int find_interval_index(float resistance, const ntc_table_entry* table, uint16_t table_size) {
int left = 0; 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 get_temperature_from_adc_with_table(uint16_t adc_value, eAlg alg, eNtcTable table_type, float r1) {
float resistance = calculate_resistance(adc_value, r1); float resistance = calculate_resistance(adc_value, r1);
if (resistance <= 0.0f) { if (resistance <= 0.0f || isinf(resistance)) {
return -273.15f; // Ошибка return -273.15f; // Ошибка
} }
@ -361,6 +413,16 @@ uint16_t get_voltage_from_adc(uint16_t adc_value) {
return adc_to_voltage_mv(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) { uint16_t get_voltage_fast_for_table(uint16_t adc_value, eNtcTable table_type) {
if (!is_table_initialized(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) { void init_fast_lookup_table(eNtcTable table_type, float r1, eAlg use_alg) {
uint16_t table_size; uint16_t table_size;
int16_t start_temp, end_temp; 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; g_fast_tables.ambient_r1 = r1;
} }
// Находим рабочий диапазон АЦП // Находим рабочий диапазон ADC
uint16_t min_valid_adc = 0; 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; // Минимальное сопротивление (макс. температура)
// Значения для граничных точек // Ищем минимальное ADC, при котором сопротивление <= max_resistance
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++) { for (uint16_t adc = 1; adc < ADC_MAX; adc++) {
float resistance = calculate_resistance(adc, r1); float resistance = calculate_resistance(adc, r1);
float temp = get_temperature_from_adc_with_table(adc, use_alg, table_type, r1); if (resistance <= max_resistance && !isinf(resistance)) {
if (temp >= start_temp && temp <= end_temp) {
min_valid_adc = adc; min_valid_adc = adc;
temp_at_min_adc = temp;
res_at_min_adc = resistance;
break; break;
} }
} }
// Ищем максимальное АЦП, при котором температура <= end_temp (минимальная температура) // Ищем максимальное ADC, при котором сопротивление >= min_resistance
for (uint16_t adc = (uint16_t)(ADC_MAX - 1); adc > 0; adc--) { for (uint16_t adc = (uint16_t)(ADC_MAX - 1); adc > 0; adc--) {
float resistance = calculate_resistance(adc, r1); float resistance = calculate_resistance(adc, r1);
float temp = get_temperature_from_adc_with_table(adc, use_alg, table_type, r1); if (resistance >= min_resistance && resistance < INFINITY) {
if (temp >= start_temp && temp <= end_temp) {
max_valid_adc = adc; max_valid_adc = adc;
temp_at_max_adc = temp;
res_at_max_adc = resistance;
break; 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 adc_step = ((uint32_t)(ADC_MAX - 1) * 1000) / (TABLE_SIZE_LOOKUP - 1);
uint32_t current_adc = 0; uint32_t current_adc = 0;
uint16_t prev_adc = 0; uint16_t prev_adc = 0;
for (uint16_t i = 0; i < TABLE_SIZE_LOOKUP; i++) { for (uint16_t i = 0; i < TABLE_SIZE_LOOKUP; i++) {
uint16_t adc; uint16_t adc;
float resistance;
float temp;
// Определяем ADC для текущей записи
if (i == 0) { if (i == 0) {
adc = 0; adc = 0;
current_adc = 0;
prev_adc = 0;
} else if (i == TABLE_SIZE_LOOKUP - 1) { } else if (i == TABLE_SIZE_LOOKUP - 1) {
adc = (uint16_t)(ADC_MAX - 1); adc = (uint16_t)(ADC_MAX - 1);
} else { } else {
current_adc += adc_step; current_adc += adc_step;
adc = (uint16_t)(current_adc / 1000); 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) { if (adc <= min_valid_adc) {
// Зона низких ADC (высокая температура) - используем граничное значение // Зона низких ADC (высокая температура) - насыщение
resistance = res_at_min_adc; resistance = min_resistance;
temp = temp_at_min_adc; temp = (float)end_temp; // Максимальная температура (+85°C)
} else if (adc >= max_valid_adc) { }
// Зона высоких ADC (низкая температура) - используем граничное значение else if (adc >= max_valid_adc) {
resistance = res_at_max_adc; // Зона высоких ADC (низкая температура) - насыщение
temp = temp_at_max_adc; resistance = max_resistance;
} else { temp = (float)start_temp; // Минимальная температура (-40°C)
// В рабочем диапазоне - вычисляем нормально }
else {
// Рабочая зона - нормальный расчет
resistance = calculate_resistance(adc, r1); 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].adc_value = adc;
fast_table[i].temp_c = (int16_t)(temp * 10.0f); fast_table[i].temp_c = (int16_t)(temp * 10.0f);
fast_table[i].resistance_ohm = resistance; 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; fast_table[i].table_type = table_type;
prev_adc = adc; 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) { void set_active_config(eNtcTable table_type, float r1) {
active_config.table_type = table_type; active_config.table_type = table_type;
active_config.r1 = r1; active_config.r1 = r1;
active_config.vcc_mv = VCC_DIVIDER_MV;
active_config.vref_mv = VREF_MV;
uint16_t table_size; uint16_t table_size;
get_table_by_type(table_type, &table_size, &active_config.start_temp, &active_config.end_temp); 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; 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) { int16_t get_temperature_log_fast_for_table(uint16_t adc_value, eNtcTable table_type) {
if (!is_table_initialized(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) { while (left <= right) {
int mid = left + (right - left) / 2; 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) { temperature_c10 >= fast_table[mid + 1].temp_c) {
// Нашли интервал // Нашли интервал
int16_t temp_high = fast_table[mid].temp_c; int16_t temp_high = fast_table[mid].temp_c;