2026-09-16 完成、当时未提交,这里补提交(原样,不含 09-27 上板的修改)。 - 器件层 / 拓扑层整体从 R1 换到 R2:TCA9548A 分通道、五路同址 INA226 0x40 + 输入侧 0x43、 刀片 PCA9555(VBUS_EN 挪到 P0.4)、CH224A 只读驱动、风扇闭环 t_thermal - 新增 i2cbus(mux 原子事务)、slot(刀片 I/O 门面)、host/qtest.sh - 74HC595 链退役(代码与测试保留) - 判据阈值与时间常量未动;19 个 ctest 全绿,idf.py build 通过 Co-Authored-By: Claude Opus 5.5 <[email protected]>
176 行
5.3 KiB
C
176 行
5.3 KiB
C
/* PWM 的假实现:只记住配置与占空比。宿主机专用。 */
|
||
#include <string.h>
|
||
|
||
#include "sim_hal.h"
|
||
|
||
typedef struct {
|
||
bool configured;
|
||
int pin;
|
||
uint32_t freq_hz;
|
||
uint16_t permille;
|
||
} ch_t;
|
||
|
||
static ch_t s_ch[HAL_PWM_CH_MAX];
|
||
|
||
/* ---- 假风扇(模型说明见 sim_hal.h)--------------------------------------- */
|
||
|
||
typedef struct {
|
||
bool attached;
|
||
int pwm_ch;
|
||
int en_pin;
|
||
uint32_t rpm_full;
|
||
uint32_t rpm_min;
|
||
uint8_t ppr;
|
||
bool gated; /* 关断期不吐脉冲 */
|
||
bool stalled;
|
||
uint64_t last_us; /* 上一次结算的时刻 */
|
||
uint64_t frac; /* 边沿的小数部分,按 1e6 缩放(整数积分,不引浮点) */
|
||
uint32_t edges;
|
||
} fan_t;
|
||
|
||
static fan_t s_fan[SIM_FAN_MAX];
|
||
|
||
static void fan_sync(void);
|
||
|
||
void sim_pwm_reset_internal(void); /* 只给 sim_hal_reset() 用 */
|
||
|
||
void sim_pwm_reset_internal(void)
|
||
{
|
||
memset(s_ch, 0, sizeof(s_ch));
|
||
memset(s_fan, 0, sizeof(s_fan)); /* 换了一块新板子 ⇒ 风扇也是新的 */
|
||
}
|
||
|
||
static bool valid(int ch) { return ch >= 0 && ch < HAL_PWM_CH_MAX; }
|
||
|
||
static bool fan_valid(int idx) { return idx >= 0 && idx < SIM_FAN_MAX && s_fan[idx].attached; }
|
||
|
||
/* 这一刻这只风扇的转速。三个把它变成 0 的条件各对应一条真实故障。 */
|
||
static uint32_t fan_rpm_now(const fan_t *f)
|
||
{
|
||
if (f->stalled) {
|
||
return 0; /* 堵转 / 风扇线没插 */
|
||
}
|
||
if (f->en_pin >= 0 && sim_gpio_level(f->en_pin) != 1) {
|
||
return 0; /* 12V 总开关没开 —— 复位态就是这个样子 */
|
||
}
|
||
int g = sim_pwm_permille(f->pwm_ch);
|
||
if (g < 0) {
|
||
/* PWM 还没配:板上 R734 把 Q6 栅极拉低 ⇒ 风扇 PWM 线被风扇自己拉高
|
||
⇒ **满速**。这是安全侧,也是固件还没接管时的真实行为。 */
|
||
g = 0;
|
||
}
|
||
const uint32_t fan_duty = 1000u - (uint32_t)g; /* Q6 反相 */
|
||
return f->rpm_min + (f->rpm_full - f->rpm_min) * fan_duty / 1000u;
|
||
}
|
||
|
||
/* 把「上次结算到现在」这段时间的边沿积出来。 */
|
||
static void fan_sync(void)
|
||
{
|
||
const uint64_t now = hal_time_now_us();
|
||
for (int i = 0; i < SIM_FAN_MAX; i++) {
|
||
fan_t *f = &s_fan[i];
|
||
if (!f->attached) {
|
||
continue;
|
||
}
|
||
if (now <= f->last_us) {
|
||
f->last_us = now;
|
||
continue;
|
||
}
|
||
uint64_t dt = now - f->last_us;
|
||
f->last_us = now;
|
||
|
||
const uint32_t rpm = fan_rpm_now(f);
|
||
if (rpm == 0) {
|
||
continue;
|
||
}
|
||
if (f->gated) {
|
||
/* 关断期没有回流 ⇒ 脉冲只出现在导通的那一部分时间里。
|
||
⚠️ 这一行就是 TR-6 那条误报的来源:占空比越低,同样一秒里
|
||
吐出来的脉冲越少,而风扇转得好好的。 */
|
||
int g = sim_pwm_permille(f->pwm_ch);
|
||
const uint32_t fan_duty = (g < 0) ? 1000u : (1000u - (uint32_t)g);
|
||
dt = dt * fan_duty / 1000u;
|
||
}
|
||
/* edges = rpm/60 × ppr × dt[s],按 1e6 缩放累加。 */
|
||
f->frac += (uint64_t)rpm * f->ppr * dt / 60u;
|
||
f->edges += (uint32_t)(f->frac / 1000000u);
|
||
f->frac %= 1000000u;
|
||
}
|
||
}
|
||
|
||
void sim_fan_attach(int idx, int pwm_ch, int en_pin,
|
||
uint32_t rpm_full, uint32_t rpm_min, uint8_t ppr)
|
||
{
|
||
if (idx < 0 || idx >= SIM_FAN_MAX || ppr == 0) {
|
||
return;
|
||
}
|
||
s_fan[idx] = (fan_t){
|
||
.attached = true, .pwm_ch = pwm_ch, .en_pin = en_pin,
|
||
.rpm_full = rpm_full, .rpm_min = rpm_min, .ppr = ppr,
|
||
.gated = true, .last_us = hal_time_now_us(),
|
||
};
|
||
}
|
||
|
||
void sim_fan_set_gated(int idx, bool on)
|
||
{
|
||
fan_sync();
|
||
if (fan_valid(idx)) {
|
||
s_fan[idx].gated = on;
|
||
}
|
||
}
|
||
|
||
void sim_fan_set_stalled(int idx, bool on)
|
||
{
|
||
fan_sync();
|
||
if (fan_valid(idx)) {
|
||
s_fan[idx].stalled = on;
|
||
}
|
||
}
|
||
|
||
uint32_t sim_fan_edges(int idx)
|
||
{
|
||
fan_sync();
|
||
return fan_valid(idx) ? s_fan[idx].edges : 0;
|
||
}
|
||
|
||
uint32_t sim_fan_rpm(int idx)
|
||
{
|
||
fan_sync();
|
||
return fan_valid(idx) ? fan_rpm_now(&s_fan[idx]) : 0;
|
||
}
|
||
|
||
hal_err_t hal_pwm_config(int ch, int pin, uint32_t freq_hz)
|
||
{
|
||
if (!valid(ch) || pin < 0 || pin >= SIM_GPIO_PINS || freq_hz == 0) {
|
||
return HAL_ERR_ARG;
|
||
}
|
||
fan_sync(); /* 配置之前那段时间按「还没配 ⇒ 满速」结算(见 fan_rpm_now) */
|
||
s_ch[ch] = (ch_t){ .configured = true, .pin = pin, .freq_hz = freq_hz, .permille = 0 };
|
||
return HAL_OK;
|
||
}
|
||
|
||
hal_err_t hal_pwm_set_permille(int ch, uint16_t permille)
|
||
{
|
||
if (!valid(ch) || !s_ch[ch].configured || permille > 1000) {
|
||
return HAL_ERR_ARG;
|
||
}
|
||
/* 先按**旧**占空比把已经过去的那段时间结算掉,再换新值 ——
|
||
不然改占空比的那一刻会把过去一整秒的脉冲按新占空比重算一遍。 */
|
||
fan_sync();
|
||
s_ch[ch].permille = permille;
|
||
return HAL_OK;
|
||
}
|
||
|
||
hal_err_t hal_pwm_get_permille(int ch, uint16_t *permille)
|
||
{
|
||
if (!valid(ch) || !s_ch[ch].configured || permille == NULL) {
|
||
return HAL_ERR_ARG;
|
||
}
|
||
*permille = s_ch[ch].permille;
|
||
return HAL_OK;
|
||
}
|
||
|
||
int sim_pwm_pin(int ch) { return valid(ch) && s_ch[ch].configured ? s_ch[ch].pin : -1; }
|
||
uint32_t sim_pwm_freq(int ch) { return valid(ch) && s_ch[ch].configured ? s_ch[ch].freq_hz : 0; }
|
||
int sim_pwm_permille(int ch) { return valid(ch) && s_ch[ch].configured ? (int)s_ch[ch].permille : -1; }
|