From 7f9dbfd683c60253e53820effe008b07d59aa641 Mon Sep 17 00:00:00 2001 From: Drath Date: Mon, 13 Apr 2026 00:24:56 +0200 Subject: [PATCH] lil refactor for clarity --- README.md | 10 +++-- src/fan_driver.h | 97 ++++++++++++++++++++++++------------------------ src/funconfig.h | 4 +- src/i2c.h | 8 ++-- src/main.c | 41 +++++++++----------- 5 files changed, 79 insertions(+), 81 deletions(-) diff --git a/README.md b/README.md index 05318a0..804aabf 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ This one is based on WCH CH32V003 and it's (should be) much better than the shit The `i2c_driver.h` is copied wholesale from ch32fun i2c_slave examples folder. It literally just works. +RPM readings are getting unstable at ~310-320 RPM but that's probably okay. + ## How compile? There's a Makefile shithead @@ -62,7 +64,7 @@ State equal to 0 means all is good. | 1 (000000x0) | Fan stalled. **0** - fan operating normally. **1** - fan stalled. | ### Mode register -| Bit | Description | -|--------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| 0 (0000000x) | Mode of operation: **0** - Constant duty cycle mode, the controller will keep the fan at a duty cycle set in register 0x06 and not change it automatically. **1** - Target RPM mode, the controller will target RPM set in register 0x04. **This is the initial mode.** | -| 1 (000000x0) | Fan power disable: **0** - Fan operates normally. **1** - 12V supply to the fan is cut. | \ No newline at end of file +| Bit | Description | +|--------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| 0 (0000000x) | Mode of operation: **0** - Constant duty cycle mode, the controller will keep the fan at a duty cycle set in register 0x06 and not change it automatically. **1** - Constant RPM mode, the controller continuously adjust PWM duty until target RPM is reached. **This is the initial mode.** | +| 1 (000000x0) | Fan power disable: **0** - Fan operates normally. **1** - 12V supply to the fan is cut. | \ No newline at end of file diff --git a/src/fan_driver.h b/src/fan_driver.h index ec5cbd7..899600a 100644 --- a/src/fan_driver.h +++ b/src/fan_driver.h @@ -17,16 +17,12 @@ uint16_t fan_driver_target_rpm = 0; uint16_t fan_driver_current_rpm = 0; bool fan_driver_is_stalled = false; bool fan_driver_is_steady = false; -bool fan_driver_powered_down = false; - -enum { - MODE_CONSTANT_DUTY = 0, - MODE_RPM_TARGET = 1 -} fan_driver_control_mode; - +volatile bool fan_driver_powered_down = false; +volatile bool fan_driver_constant_duty = false; // true = constant rpm mode // callback for the pin interrupt void TIM2_IRQHandler(void) __attribute__((interrupt)); + void TIM2_IRQHandler(void) { static uint16_t _fan_driver_last_capture = 0; if (TIM2->INTFR & TIM_IT_CC2) { @@ -38,14 +34,15 @@ void TIM2_IRQHandler(void) { } } -void fan_driver_init(void) { +static void fan_driver_init(void) { const uint8_t pwm_pin = 2; // also need to change TIM1->CCER const uint8_t tacho_pin = 3; const uint8_t power_pin = 6; // also change in set_power func // enable clock gating to relevant periphs RCC->APB1PCENR |= RCC_APB1Periph_TIM2; - RCC->APB2PCENR |= RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD | RCC_APB2Periph_TIM1 | RCC_APB2Periph_AFIO; // enable clock gating for gpio and pwm timer + RCC->APB2PCENR |= RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD | RCC_APB2Periph_TIM1 | RCC_APB2Periph_AFIO; + // enable clock gating for gpio and pwm timer // power pin init GPIOC->CFGLR &= ~(0xf << (4 * power_pin)); @@ -57,35 +54,35 @@ void fan_driver_init(void) { // we init pin register to 1111, then mask the features we need GPIOD->CFGLR &= ~(0xf << (4 * pwm_pin)); GPIOD->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP_AF) << (4 * pwm_pin); - TIM1->PSC = 0x0000; // prescaler = 0 - TIM1->ATRLR = pwm_timer_period - 1; // this value gets auto-reloaded on timer reset - TIM1->SWEVGR |= TIM_UG; // reload immediately - TIM1->CCER |= TIM_CC1E | TIM_CC1NP; // enable timer output t1ch1, positive polarity + TIM1->PSC = 0x0000; // prescaler = 0 + TIM1->ATRLR = pwm_timer_period - 1; // this value gets auto-reloaded on timer reset + TIM1->SWEVGR |= TIM_UG; // reload immediately + TIM1->CCER |= TIM_CC1E | TIM_CC1NP; // enable timer output t1ch1, positive polarity TIM1->CHCTLR1 |= TIM_OC1M_2 | TIM_OC1M_1; // set compare mode, set output? - TIM1->CH1CVR = TIM1->ATRLR; // initial duty at full speed - TIM1->BDTR |= TIM_MOE; // enable outputs - TIM1->CTLR1 |= TIM_CEN; // enable timer + TIM1->CH1CVR = TIM1->ATRLR; // initial duty at full speed + TIM1->BDTR |= TIM_MOE; // enable outputs + TIM1->CTLR1 |= TIM_CEN; // enable timer // tacho stuff init // set up GPIO (pd3, t2ch2) GPIOD->CFGLR &= ~(0xf << (4 * tacho_pin)); GPIOD->CFGLR |= (GPIO_CNF_IN_PUPD) << (4 * tacho_pin); // pullup/down mode - GPIOD->OUTDR |= 1 << tacho_pin; // 1 - pullup, 0 - pulldown + GPIOD->OUTDR |= 1 << tacho_pin; // 1 - pullup, 0 - pulldown // reset timer to reinint RCC->APB1PRSTR |= RCC_APB1Periph_TIM2; RCC->APB1PRSTR &= ~RCC_APB1Periph_TIM2; // timer setup - TIM2->PSC = (FUNCONF_SYSTEM_CORE_CLOCK / 500000) - 1; // prescaler, 2us tick - TIM2->SWEVGR |= TIM_UG; // reload timer immediately - TIM2->CHCTLR1 &= ~(0xff << 8); // reset config bits + TIM2->PSC = (FUNCONF_SYSTEM_CORE_CLOCK / 500000) - 1; // prescaler, 2us tick + TIM2->SWEVGR |= TIM_UG; // reload timer immediately + TIM2->CHCTLR1 &= ~(0xff << 8); // reset config bits TIM2->CHCTLR1 |= TIM_CC2S_0 | TIM_IC2F_2 | TIM_IC2F_3; // capture compare as input, set filter to 1100 - TIM2->CCER |= TIM_CC2E; // enable capture on capture compare ch2 (cc2) - TIM2->DMAINTENR |= TIM_IT_CC2; // trigger interrupt on cc2 + TIM2->CCER |= TIM_CC2E; // enable capture on capture compare ch2 (cc2) + TIM2->DMAINTENR |= TIM_IT_CC2; // trigger interrupt on cc2 // enable interrupt, enable timer - NVIC->IENR[((uint32_t)(TIM2_IRQn) >> 5)] = (1 << ((uint32_t)(TIM2_IRQn) & 0x1F)); // NVIC_EnableIRQ(TIM2_IRQn); + NVIC->IENR[((uint32_t) (TIM2_IRQn) >> 5)] = (1 << ((uint32_t) (TIM2_IRQn) & 0x1F)); // NVIC_EnableIRQ(TIM2_IRQn); TIM2->CTLR1 |= TIM_CEN; // enable timer } @@ -105,36 +102,40 @@ static void fan_driver_set_power(const bool power) { GPIOC->BSHR = (1 << (6 + 16 * !power)); } -void fan_driver_control_loop(void) { - static uint32_t last_delta_idx = 0xffffffff; - - fan_driver_is_stalled = (_delta_idx == last_delta_idx); - - if(fan_driver_is_stalled) { - fan_driver_current_rpm = 0; - return; - } - last_delta_idx = _delta_idx; - - fan_driver_set_power(!fan_driver_powered_down); - if (fan_driver_powered_down) - return; - +static void fan_driver_compute_rpm(void) { uint32_t delta = 0; for (int i = 0; i < rpm_buf_size; i++) delta += _fan_driver_delta[i]; delta /= rpm_buf_size; fan_driver_current_rpm = delta ? 30000000 / 2 / delta : 0; // each tick is 2us. one minute is 30000000 ticks, each rotation is 2 pulses - - if(fan_driver_control_mode == 1) { - const int32_t duty_trim = (fan_driver_target_rpm - fan_driver_current_rpm) / 4; - fan_driver_pwm_set_duty(fan_driver_pwm_get_duty() + duty_trim); - fan_driver_is_steady = (duty_trim == 0); - } else { - fan_driver_is_steady = true; - } - } -#endif //FAN_DRIVER_H \ No newline at end of file +static void fan_driver_control_loop(void) { + // if we need to power down, disable power pin and return + fan_driver_set_power(!fan_driver_powered_down); + if (fan_driver_powered_down) return; + + // see if fan stalled, set rpm to 0 and return if it is + static uint32_t last_delta_idx = 0xffffffff; + fan_driver_is_stalled = (_delta_idx == last_delta_idx); + last_delta_idx = _delta_idx; + if (fan_driver_is_stalled) { + fan_driver_current_rpm = 0; + return; + } + + // compute rpms then return if we're in constant duty mode + fan_driver_compute_rpm(); + if (fan_driver_constant_duty) { + fan_driver_is_steady = true; + return; + } + + // compute rpm error, adjust duty so it's 0 + const int32_t rpm_error = (fan_driver_target_rpm - fan_driver_current_rpm) / 4; + fan_driver_pwm_set_duty(fan_driver_pwm_get_duty() + rpm_error); + fan_driver_is_steady = (rpm_error == 0); +} + +#endif //FAN_DRIVER_H diff --git a/src/funconfig.h b/src/funconfig.h index 84d3533..54412cf 100644 --- a/src/funconfig.h +++ b/src/funconfig.h @@ -1,7 +1,7 @@ #ifndef FUNCONFIG_H #define FUNCONFIG_H -#define FUNCONF_USE_DEBUGPRINTF 0 -#define FUNCONF_USE_UARTPRINTF 1 +#define FUNCONF_USE_DEBUGPRINTF 1 +#define FUNCONF_USE_UARTPRINTF 0 #endif //FUNCONFIG_H \ No newline at end of file diff --git a/src/i2c.h b/src/i2c.h index 9948503..c0f654c 100644 --- a/src/i2c.h +++ b/src/i2c.h @@ -13,23 +13,23 @@ #define I2C_REG_STATE 0x0a #define I2C_REG_MODE 0x0b -static void i2c_set_register_word(volatile uint8_t* reg, const uint8_t addr, const uint16_t value) { +static void i2c_set_register_word(volatile uint8_t *reg, const uint8_t addr, const uint16_t value) { if (addr % 2) return; reg[addr] = value >> 8; reg[addr + 1] = value & 0xff; } -static uint16_t i2c_get_register_word(volatile uint8_t* reg, const uint8_t addr) { +static uint16_t i2c_get_register_word(volatile uint8_t *reg, const uint8_t addr) { if (addr % 2) return 0; return (reg[addr] << 8) + reg[addr + 1]; } -static void i2c_registers_reinit(volatile uint8_t* reg) { +static void i2c_registers_reinit(volatile uint8_t *reg) { i2c_set_register_word(reg, I2C_REG_WHOAMI, 26923); // magic value ;) i2c_set_register_word(reg, I2C_REG_MAX_DUTY, fan_driver_pwm_get_max_duty()); } -static void i2c_registers_set_defaults(volatile uint8_t* reg) { +static void i2c_registers_set_defaults(volatile uint8_t *reg) { i2c_set_register_word(reg, I2C_REG_TARGET_RPM, 750); reg[I2C_REG_MODE] = 0x01; // target rpm mode, pwm control, power enabled } diff --git a/src/main.c b/src/main.c index 26296e3..8cbf490 100644 --- a/src/main.c +++ b/src/main.c @@ -5,10 +5,10 @@ volatile uint8_t i2c_registers[16] = {0x00}; -void update_fan_from_regs(volatile uint8_t* reg) { +void update_fan_from_regs(volatile uint8_t *reg) { fan_driver_target_rpm = i2c_get_register_word(reg, I2C_REG_TARGET_RPM); fan_driver_pwm_set_duty(i2c_get_register_word(reg, I2C_REG_CURRENT_DUTY)); - fan_driver_control_mode = (reg[I2C_REG_MODE] & 0x01); + fan_driver_constant_duty = !(reg[I2C_REG_MODE] & 0x01); fan_driver_powered_down = (reg[I2C_REG_MODE] & 0x02) >> 1; } @@ -16,36 +16,32 @@ void onWrite(const uint8_t reg, const uint8_t length) { if (length == 0) return; switch (reg) { - - case I2C_REG_TARGET_RPM: - update_fan_from_regs(i2c_registers); - printf("set tgt fan speed to %d\n", fan_driver_target_rpm); - break; - - case I2C_REG_CURRENT_DUTY: + case I2C_REG_TARGET_RPM: update_fan_from_regs(i2c_registers); - printf("set duty cycle to %d\n", fan_driver_pwm_get_duty()); - break; + printf("set tgt fan speed to %d\n", fan_driver_target_rpm); + break; - case I2C_REG_MODE: + case I2C_REG_CURRENT_DUTY: update_fan_from_regs(i2c_registers); - printf("set mode register to %x. control mode is %d, powered down is %d\n", i2c_registers[I2C_REG_MODE], fan_driver_control_mode, fan_driver_powered_down); - break; + printf("set duty cycle to %d\n", fan_driver_pwm_get_duty()); + break; - default: - i2c_registers_reinit(i2c_registers); - break; + case I2C_REG_MODE: + update_fan_from_regs(i2c_registers); + printf("set mode register to %x. constant duty mode is %d, powered down is %d\n", + i2c_registers[I2C_REG_MODE], fan_driver_constant_duty, fan_driver_powered_down); + break; + + default: + i2c_registers_reinit(i2c_registers); + break; } - - printf("wrote %d bytes to reg 0x%0x\n", length, reg); } int main() { SystemInit(); printf("System bootup\n"); - fan_driver_init(); - // Initialize I2C slave funGpioInitC(); funPinMode(PC1, GPIO_CFGLR_OUT_10Mhz_AF_OD); // SDA @@ -53,8 +49,8 @@ int main() { i2c_registers_reinit(i2c_registers); i2c_registers_set_defaults(i2c_registers); update_fan_from_regs(i2c_registers); + fan_driver_init(); - fan_driver_target_rpm = i2c_get_register_word(i2c_registers, I2C_REG_TARGET_RPM); SetupI2CSlave(I2C_DEVICE_ADDR, i2c_registers, sizeof(i2c_registers), onWrite, NULL, false); while (1) { @@ -66,5 +62,4 @@ int main() { Delay_Ms(250); } - }