/* * @Date: 2025-06-23 13:04:20 * @Author: mypx * @LastEditors: mypx mypx_coder@163.com * @LastEditTime: 2025-10-27 08:42:50 * @FilePath: tec_control.c * @Description: * Copyright (c) 2025 by mypx, All Rights Reserved. */ #include "tec_control.h" #include "ad7793.h" #include "bsp_misc.h" #include "bsp_tec.h" #include "bsp_temper_sampling.h" #include "et_log.h" #include "etk_utils.h" #include "mb_interface.h" #include "pm_common.h" #include "pt100x.h" #include #include #include #include #include /* rand, RAND_MAX */ #if TEC_CONTROL_PERIOD < ADC_SAMPLE_PERIOD #error "TEC_CONTROL_PERIOD must be greater than ADC_SAMPLE_PERIOD" #endif #define TEST_TEMPER_XFER 0 /* Local helpers to avoid implicit prototypes on some embedded libc's */ static inline int _isnan_f(float v) { return v != v; } static inline float _fabs_f(float v) { return v < 0.0f ? -v : v; } /* Define AD7793 device structure */ ad7793_dev_t ad7793_dev; #if (USING_SOFT_SPI == 1) extern soft_spi_t soft_spi; #endif tec_control_t *tec_control = NULL; ad7793_hw_if_t ad7793_hw_if = { .spi_transfer = bsp_ad7793_spi_transfer, .spi_write = bsp_ad7793_spi_write, .spi_read = bsp_ad7793_spi_read, .gpio_set = bsp_ad7793_gpio_set, .gpio_get = bsp_ad7793_gpio_get, .delay_ms = bsp_ad7793_delay_ms, }; /* ------------------------------------------------------------- * AD7793 应用层配置 * 依据提供原理图的典型 RTD/PT100 测温拓扑做如下假设: * 1. 仅测量正向电压 => 使用单极 (unipolar) * 2. 传感器电流源(或恒流驱动)下 PT100 产生的电压几十 mV 级,选择增益 16: * - 内部参考 1.17V / GAIN16 满量程约 73mV,有足够余量覆盖 0~200+℃ (约 21mV~37mV) * - 若后续发现仍远低于满量程,可改为 GAIN32;若出现接近饱和则降为 GAIN8。 * 3. 启用输入缓冲 (buffered=true) 以减小前端阻抗对 ADC 的影响。 * 4. 采用内部参考 (1.17V) 简化;若外部精密参考芯片实际接入 REF+ / REF-, * 将 use_internal_ref 改为 false 并填写 external_ref (例如 2.500f 或 3.000f)。 * 5. 选择较低输出数据速率 8.33Hz 获取更好噪声性能(速率越低,数字滤波越强)。 * 6. 上电后做一次 system zero 校准,提高 offset 精度。 * ------------------------------------------------------------- */ static const ad7793_config_t g_ad7793_config = { .use_internal_ref = false, /* 现在使用板上 2.5V 外部参考 (已实测 REFIN+=2.5V) */ .external_ref = 2.500f, /* 传感器差分约 0.044V, 需保证 < Vref / Gain 选择 GAIN=4 -> 满量程 2.5/4=0.625V (安全覆盖 0.44V); 若后续电压更低可再调高增益 */ .gain = AD7793_GAIN_4, .channel = AD7793_CHANNEL_1, /* AIN1(+) - AIN1(-) */ .rate = AD7793_RATE_8_33HZ, .unipolar_mode = true, /* 只测正向信号 */ .buffered = true, /* 启用缓冲 */ .calibrate_system_zero = false /* 暂停 system zero 校准 (之前 OFFSET/FULLSC 已被污染) */ }; /* Shared data structure for temperature sampling and TEC control */ static struct { float current_temperature; bool temp_updated; rt_mutex_t data_mutex; } temp_shared_data; float tec_get_target_voltage(float r) { double numerator = 135 - 675 * r; double denominator = 390 * r + 1343; if (denominator == 0) { ET_ERR("Error: Denominator is zero for r = %.2f", r); return 0.0; } return numerator / denominator; } /** * @param voltage: Voltage value in volts * @return double: Resistance in ohms */ static double solve_resistance(double voltage) { double numerator = 135 - 1343 * voltage; double denominator = 390 * voltage + 675; if (denominator == 0) { ET_ERR("Error: Denominator is zero for v = %.2f", voltage); return 0.0; } return numerator * 1000 / denominator; } void tec_pid_init(tec_control_t *tec) { float kp = 15.0f; float ki = 0.2f; float kd = 0.05f; float threshold = 0.5f; // 积分分离阈值,可根据实际情况调整 et_integral_separation_pid_init(&tec->pid, kp, ki, kd, threshold); } float tec_pid_update(tec_control_t *tec, float set_point, float current_temp, float dt) { tec->pid.set_point = set_point; return et_integral_separation_pid_update(&tec->pid, current_temp, dt); } void tec_control_reset(void) { if (tec_control != NULL) { tec_control->target_temper = 0.0f; } } int tec_control_resume(void) { if (tec_control == NULL) { ET_ERR("TEC control not initialized"); return -1; } if (tec_control->cur_temper > tec_control->exception_temper) { ET_WARN("current temperature is too high, cannot start TEC"); return -2; } tec_control->is_control = true; return 0; } int tec_control_pause(void) { if (tec_control == RT_NULL) { ET_ERR("TEC control not initialized"); return -1; } tec_control->is_control = false; return 0; } void tec_calculate_temperature(uint32_t adc_value) { float mv = 0; double resistance; mv = ad7793_convert_to_voltage(&ad7793_dev, adc_value) * 1000; //ET_DBG("voltage:%.2f", mv); resistance = solve_resistance(mv / 1000); //ET_DBG("resistance:%.2f", resistance); //ET_PRINTF_FLOAT("resistance:", resistance); tec_control->cur_temper = pt_precise_temperature(resistance, PT100); tec_control->cur_temper = et_ema_filter_process(&tec_control->ema_filter, tec_control->cur_temper); if (_isnan_f(tec_control->cur_temper)) { ET_DBG("Invalid resistance value, cannot calculate temperature.\n"); } else { //ET_DBG("temperature:%.4f", tec_control->cur_temper); /* Update shared data */ rt_mutex_take(temp_shared_data.data_mutex, RT_WAITING_FOREVER); temp_shared_data.current_temperature = tec_control->cur_temper; temp_shared_data.temp_updated = true; rt_mutex_release(temp_shared_data.data_mutex); } } #if TEST_TEMPER_XFER float generate_random_temperature(void) { static uint32_t lfsr = 0xACE1u; /* simple LFSR to avoid rand() dependency */ lfsr ^= lfsr << 7; lfsr ^= lfsr >> 9; lfsr ^= lfsr << 8; return (lfsr & 0xFFFF) * (55.0f / 65535.0f); } #endif /* Temperature sampling thread entry function */ static void temper_sampling_thread_entry(void *parameter) { ETK_UNUSED(parameter); uint32_t adc_value = 0; const uint32_t LOW_CODE_THRESHOLD = 0x200; /* 约 0.125% 满量程,可调 */ uint32_t consecutive_err = 0; ET_INFO("Temperature sampling thread started"); while (1) { #if !TEST_TEMPER_XFER /* Wait for ADC ready and read data */ if (ad7793_wait_ready(&ad7793_dev, 1000) == true) { if (ad7793_read_data(&ad7793_dev, &adc_value)) { /* 低码过滤:避免极低噪声或参考丢失导致的虚假高温 */ if (adc_value < LOW_CODE_THRESHOLD) { ad7793_dev.low_code_drops++; ET_DBG("drop low code: 0x%06X (drops=%lu)", (unsigned)adc_value, (unsigned long)ad7793_dev.low_code_drops); } else { //ET_DBG("raw code: 0x%06X", (unsigned)adc_value); tec_calculate_temperature(adc_value); } } else { ET_ERR("Failed to read ADC data.\n"); } } mb_write_current_temp(tec_control->cur_temper); #else mb_write_current_temp(generate_random_temperature()); // test only mb_write_current_rotation(generate_random_temperature()); // test only #endif /* Delay for next sampling period */ rt_thread_mdelay(ADC_SAMPLE_PERIOD); } } // pwm: 0.0 ~ 1.0 void tec_adjust_pwm(float pwm, float cur_temper, float target_temper) { if (_fabs_f(cur_temper - target_temper) < TEC_TEMPER_PRECISE) { // within precise range, stop adjustment bsp_tec_close(); bsp_fan_stop(); return; } //bsp_fan_start(); if (cur_temper > target_temper) { // cooling bsp_tec_cooling_adjust(pwm); } else { // heating bsp_tec_heating_adjust(pwm); } } void tec_control_temperature(tec_control_t *tec) { float pid_out = 0; float current_temp; if (tec == NULL) { ET_ERR("tec is NULL"); return; } if (tec->is_control == false) return; /* Get current temperature from shared data */ rt_mutex_take(temp_shared_data.data_mutex, RT_WAITING_FOREVER); current_temp = temp_shared_data.current_temperature; temp_shared_data.temp_updated = false; rt_mutex_release(temp_shared_data.data_mutex); /* Update tec control current temperature */ tec->cur_temper = current_temp; // dt convert to seconds pid_out = tec_pid_update(tec, tec->target_temper, current_temp, TEC_CONTROL_PERIOD / 1000.0f); ET_INFO("pid_out:%.2f", pid_out); tec_adjust_pwm(pid_out, current_temp, tec->target_temper); } static void tec_control_thread_entry(void *parameter) { ETK_UNUSED(parameter); rt_tick_t last_ms = rt_tick_get_millisecond(); rt_uint32_t received; tec_pid_init(tec_control); tec_control->is_control = false; // control disabled by default //bsp_fan_start(); tec_control->target_temper = 25.0f; ET_INFO("TEC control thread started"); while (1) { // wait for control events with timeout rt_event_recv(tec_control->control_event, TEC_CONTROL_START_EVENT | TEC_CONTROL_STOP_EVENT | TEC_TEMP_UPDATE_EVENT, RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, TEC_CONTROL_PERIOD / 2, &received); // handle received events if (received & TEC_CONTROL_START_EVENT) { tec_control_resume(); ET_INFO("TEC control started by event"); } if (received & TEC_CONTROL_STOP_EVENT) { tec_control_pause(); ET_INFO("TEC control stopped by event"); } if (received & TEC_TEMP_UPDATE_EVENT) { ET_INFO("Target temperature updated to: %.2f°C", tec_control->target_temper); } // perform temperature control at specified period if (rt_tick_get_millisecond() - last_ms > TEC_CONTROL_PERIOD) { last_ms = rt_tick_get_millisecond(); tec_control_temperature(tec_control); } } } int tec_control_sercie_start(tec_control_t *tec) { (void)tec; /* unused parameter for now */ rt_thread_t temp_tid; rt_thread_t ctrl_tid; (void)ctrl_tid; // create temperature sampling thread temp_tid = rt_thread_create("temp-sample", temper_sampling_thread_entry, RT_NULL, TEMP_SAMPLE_THREAD_STACK_SIZE, TEMP_SAMPLE_THREAD_PRIORITY, TEMP_SAMPLE_THREAD_TIMESLICE); if (temp_tid == RT_NULL) { ET_ERR("Failed to create temperature sampling thread"); return -6; } rt_thread_startup(temp_tid); /* create TEC control thread */ ctrl_tid = rt_thread_create("tec-control", tec_control_thread_entry, RT_NULL, TEC_CONTROL_THREAD_STACK_SIZE, TEC_CONTROL_THREAD_PRIORITY, TEC_CONTROL_THREAD_TIMESLICE); if (ctrl_tid == RT_NULL) { ET_ERR("Failed to create tec-control thread"); return -7; } rt_thread_startup(ctrl_tid); return 0; } int tec_control_init(tec_control_t *tec) { if (tec == NULL) { ET_ERR("tec is NULL"); return -1; } tec_control = tec; et_ema_filter_init(&tec_control->ema_filter, EMA_FILTER_ALPHA); // create event object for temperature control tec_control->control_event = rt_event_create("tec_event", RT_IPC_FLAG_FIFO); if (tec_control->control_event == RT_NULL) { ET_ERR("Failed to create TEC control event"); return -3; } // create mutex for shared data protection temp_shared_data.data_mutex = rt_mutex_create("temp_mutex", RT_IPC_FLAG_FIFO); if (temp_shared_data.data_mutex == RT_NULL) { ET_ERR("Failed to create temperature data mutex"); return -5; } // initialize shared data temp_shared_data.current_temperature = 25.0f; temp_shared_data.temp_updated = false; #if (USING_SOFT_SPI == 1) if (ad7793_init(&ad7793_dev, &ad7793_hw_if, soft_spi.cs_pin, &g_ad7793_config)) #else if (ad7793_init(&ad7793_dev, &ad7793_hw_if, SPI3_AD7793_CS_Pin, &g_ad7793_config)) #endif { rt_kprintf("AD7793 device initialized successfully.\n"); } else { rt_kprintf("Failed to initialize AD7793 device.\n"); return -2; } return 0; }