polarimeter_software/User/app/tec_control.c

438 lines
13 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* @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;
}