438 lines
13 KiB
C
438 lines
13 KiB
C
/*
|
||
* @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 <math.h>
|
||
#include <stdbool.h>
|
||
#include <stdint.h>
|
||
#include <stdio.h>
|
||
#include <stdlib.h> /* 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;
|
||
}
|