mirror of
https://github.com/infiniteCable2/panda.git
synced 2026-02-18 09:13:52 +08:00
Change fan to use pure pwm (#2267)
* set cuatro fan settings * set cuatro to use pure pwm * use pwm on all boards * remove fan max rpm configs, use pure pwm * add comment about cooldown * change max rpm in python side * remove reference to max_fan_pwm in fan.h * fully refactor fan configs into "has_fan" * increase limits on fan test to 15% either way * only tres for now * doesn't matter * not relevant anymore --------- Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
This commit is contained in:
@@ -29,10 +29,9 @@ struct board {
|
||||
const uint8_t led_pin[3];
|
||||
const uint8_t led_pwm_channels[3]; // leave at 0 to disable PWM
|
||||
const bool has_spi;
|
||||
const uint16_t fan_max_rpm;
|
||||
const bool has_fan;
|
||||
const uint16_t avdd_mV;
|
||||
const uint8_t fan_enable_cooldown_time;
|
||||
const uint8_t fan_max_pwm;
|
||||
board_init init;
|
||||
board_init_bootloader init_bootloader;
|
||||
board_enable_can_transceiver enable_can_transceiver;
|
||||
|
||||
@@ -132,8 +132,7 @@ static harness_configuration cuatro_harness_config = {
|
||||
board board_cuatro = {
|
||||
.harness_config = &cuatro_harness_config,
|
||||
.has_spi = true,
|
||||
.fan_max_rpm = 12500U,
|
||||
.fan_max_pwm = 99U, // it can go up to 14k RPM, but 99% -> 100% is very non-linear
|
||||
.has_fan = true,
|
||||
.avdd_mV = 1800U,
|
||||
.fan_enable_cooldown_time = 3U,
|
||||
.init = cuatro_init,
|
||||
|
||||
@@ -115,8 +115,7 @@ board board_red = {
|
||||
.set_bootkick = unused_set_bootkick,
|
||||
.harness_config = &red_harness_config,
|
||||
.has_spi = false,
|
||||
.fan_max_rpm = 0U,
|
||||
.fan_max_pwm = 100U,
|
||||
.has_fan = false,
|
||||
.avdd_mV = 3300U,
|
||||
.fan_enable_cooldown_time = 0U,
|
||||
.init = red_init,
|
||||
|
||||
@@ -155,8 +155,7 @@ static harness_configuration tres_harness_config = {
|
||||
board board_tres = {
|
||||
.harness_config = &tres_harness_config,
|
||||
.has_spi = true,
|
||||
.fan_max_rpm = 6600U,
|
||||
.fan_max_pwm = 100U,
|
||||
.has_fan = true,
|
||||
.avdd_mV = 1800U,
|
||||
.fan_enable_cooldown_time = 3U,
|
||||
.init = tres_init,
|
||||
|
||||
@@ -5,10 +5,13 @@ struct fan_state_t fan_state;
|
||||
static const uint8_t FAN_TICK_FREQ = 8U;
|
||||
|
||||
void fan_set_power(uint8_t percentage) {
|
||||
fan_state.target_rpm = ((current_board->fan_max_rpm * CLAMP(percentage, 0U, 100U)) / 100U);
|
||||
if (percentage > 0U) {
|
||||
fan_state.power = CLAMP(percentage, 20U, 100U);
|
||||
} else {
|
||||
fan_state.power = 0U;
|
||||
}
|
||||
}
|
||||
|
||||
void llfan_init(void);
|
||||
void fan_init(void) {
|
||||
fan_state.cooldown_counter = current_board->fan_enable_cooldown_time * FAN_TICK_FREQ;
|
||||
llfan_init();
|
||||
@@ -16,9 +19,7 @@ void fan_init(void) {
|
||||
|
||||
// Call this at FAN_TICK_FREQ
|
||||
void fan_tick(void) {
|
||||
const float FAN_I = 6.5f;
|
||||
|
||||
if (current_board->fan_max_rpm > 0U) {
|
||||
if (current_board->has_fan) {
|
||||
// Measure fan RPM
|
||||
uint16_t fan_rpm_fast = fan_state.tach_counter * (60U * FAN_TICK_FREQ / 4U); // 4 interrupts per rotation
|
||||
fan_state.tach_counter = 0U;
|
||||
@@ -31,8 +32,8 @@ void fan_tick(void) {
|
||||
print("\n");
|
||||
#endif
|
||||
|
||||
// Cooldown counter
|
||||
if (fan_state.target_rpm > 0U) {
|
||||
// Cooldown counter to prevent noise on tachometer line.
|
||||
if (fan_state.power > 0U) {
|
||||
fan_state.cooldown_counter = current_board->fan_enable_cooldown_time * FAN_TICK_FREQ;
|
||||
} else {
|
||||
if (fan_state.cooldown_counter > 0U) {
|
||||
@@ -40,18 +41,8 @@ void fan_tick(void) {
|
||||
}
|
||||
}
|
||||
|
||||
// Update controller
|
||||
if (fan_state.target_rpm == 0U) {
|
||||
fan_state.error_integral = 0.0f;
|
||||
} else {
|
||||
float error = (fan_state.target_rpm - fan_rpm_fast) / ((float) current_board->fan_max_rpm);
|
||||
fan_state.error_integral += FAN_I * error;
|
||||
}
|
||||
fan_state.error_integral = CLAMP(fan_state.error_integral, 0U, current_board->fan_max_pwm);
|
||||
fan_state.power = fan_state.error_integral;
|
||||
|
||||
// Set PWM and enable line
|
||||
pwm_set(TIM3, 3, fan_state.power);
|
||||
current_board->set_fan_enabled((fan_state.target_rpm > 0U) || (fan_state.cooldown_counter > 0U));
|
||||
current_board->set_fan_enabled((fan_state.power > 0U) || (fan_state.cooldown_counter > 0U));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
struct fan_state_t {
|
||||
uint16_t tach_counter;
|
||||
uint16_t rpm;
|
||||
uint16_t target_rpm;
|
||||
uint8_t power;
|
||||
float error_integral;
|
||||
uint8_t cooldown_counter;
|
||||
|
||||
@@ -294,7 +294,7 @@ int main(void) {
|
||||
microsecond_timer_init();
|
||||
|
||||
current_board->set_siren(false);
|
||||
if (current_board->fan_max_rpm > 0U) {
|
||||
if (current_board->has_fan) {
|
||||
fan_init();
|
||||
}
|
||||
|
||||
|
||||
@@ -131,7 +131,7 @@ class Panda:
|
||||
|
||||
MAX_FAN_RPMs = {
|
||||
HW_TYPE_TRES: 6600,
|
||||
HW_TYPE_CUATRO: 12500,
|
||||
HW_TYPE_CUATRO: 5000,
|
||||
}
|
||||
|
||||
HARNESS_STATUS_NC = 0
|
||||
|
||||
@@ -4,18 +4,21 @@ import pytest
|
||||
from panda import Panda
|
||||
|
||||
pytestmark = [
|
||||
pytest.mark.test_panda_types(Panda.INTERNAL_DEVICES)
|
||||
# TODO: re-enable this once we update the HITL devices
|
||||
#pytest.mark.test_panda_types(Panda.INTERNAL_DEVICES)
|
||||
pytest.mark.test_panda_types([Panda.HW_TYPE_TRES])
|
||||
]
|
||||
|
||||
@pytest.mark.timeout(2*60)
|
||||
def test_fan_controller(p):
|
||||
def test_fan_curve(p):
|
||||
# ensure fan curve is (roughly) linear
|
||||
|
||||
for power in (30, 50, 80, 100):
|
||||
p.set_fan_power(0)
|
||||
while p.get_fan_rpm() > 0:
|
||||
time.sleep(0.1)
|
||||
|
||||
# wait until fan spins up (and recovers if needed),
|
||||
# then wait a bit more for the RPM to converge
|
||||
# wait until fan spins up, then wait a bit more for the RPM to converge
|
||||
p.set_fan_power(power)
|
||||
for _ in range(20):
|
||||
time.sleep(1)
|
||||
@@ -24,7 +27,7 @@ def test_fan_controller(p):
|
||||
time.sleep(5)
|
||||
|
||||
expected_rpm = Panda.MAX_FAN_RPMs[bytes(p.get_type())] * power / 100
|
||||
assert 0.9 * expected_rpm <= p.get_fan_rpm() <= 1.1 * expected_rpm
|
||||
assert 0.75 * expected_rpm <= p.get_fan_rpm() <= 1.25 * expected_rpm
|
||||
|
||||
def test_fan_cooldown(p):
|
||||
# if the fan cooldown doesn't work, we get high frequency noise on the tach line
|
||||
@@ -35,21 +38,3 @@ def test_fan_cooldown(p):
|
||||
for _ in range(5):
|
||||
assert p.get_fan_rpm() <= Panda.MAX_FAN_RPMs[bytes(p.get_type())]
|
||||
time.sleep(0.5)
|
||||
|
||||
def test_fan_overshoot(p):
|
||||
|
||||
# make sure it's stopped completely
|
||||
p.set_fan_power(0)
|
||||
while p.get_fan_rpm() > 0:
|
||||
time.sleep(0.1)
|
||||
|
||||
# set it to 30% power to mimic going onroad
|
||||
p.set_fan_power(30)
|
||||
max_rpm = 0
|
||||
for _ in range(50):
|
||||
max_rpm = max(max_rpm, p.get_fan_rpm())
|
||||
time.sleep(0.1)
|
||||
|
||||
# tolerate 10% overshoot
|
||||
expected_rpm = Panda.MAX_FAN_RPMs[bytes(p.get_type())] * 30 / 100
|
||||
assert max_rpm <= 1.1 * expected_rpm, f"Fan overshoot: {(max_rpm / expected_rpm * 100) - 100:.1f}%"
|
||||
|
||||
Reference in New Issue
Block a user