FEATIRE COMPLETE?

This commit is contained in:
2026-04-12 23:29:57 +02:00
parent 82e43b67da
commit 4abb1df03c
4 changed files with 63 additions and 43 deletions

View File

@@ -1,7 +1,7 @@
# I2C Fan Controller v2
~because I found a cool new microcontroller~
~ because I found a cool new microcontroller ~
This one is based on WCH CH32V003 and it's (should be) much better than the shitty ATTiny one.
@@ -12,25 +12,40 @@ There's a Makefile shithead
We compile on Debian/ubuntu. Other than the usual `build-essential` stuff you need also
```bash
sudo apt install gcc-riscv64-unknown-elf libnewlib-dev libudev-dev
sudo apt install gcc-riscv64-unknown-elf libnewlib-dev libudev-dev libusb-1.0-0-dev
```
There is a distrobox.ini provided.
You will also need to clone the [`ch32fun repo`](https://github.com/cnlohr/ch32fun) somewhere and modify the `Makefile`.
You will also need to compile `minichlink`. Included in the `ch32fun` repo.
To flash you will also need to compile `minichlink`. Included in the `ch32fun` repo.
### ???..
Or just use PlatformIO idc. It has debugging.
## Pins
For the TSSOP20 package, CH32V003FxPx
| Pin # | Port | Function |
|-------|------|----------|
| 3 | PD5 | TX |
| 4 | PD7 | NRST |
| 11 | PC1 | SDA |
| 12 | PC2 | SCL |
| 16 | PC6 | POWER_EN |
| 18 | PD1 | SWIO |
| 19 | PD2 | PWM_OUT |
| 20 | PD3 | TACH_IN |
## Register index
Registers are 8 bit each, but they hold 16-bit values.
Registers are 8 bit each, but most of them hold 16-bit values.
Bytes in even registers are high, bytes in odd registers are low.
| Register address | Function | R/W |
|------------------|-----------------------------------------------------------|-----|
| 0x00, 0x01 | whoami | RO |
| 0x00, 0x01 | whoami (always returns 0x69 0x2b) | RO |
| 0x02, 0x03 | Current fan speed (RPM) | RO |
| 0x04, 0x05 | Target fan speed (RPM). Initial default speed is 750 RPM. | R/W |
| 0x06, 0x07 | Current duty cycle | R/W |
@@ -39,19 +54,15 @@ Bytes in even registers are high, bytes in odd registers are low.
| 0x0b | Mode register | R/W |
### State register
State equal to 0 means all is good.
| Bit | Description |
|--------------|----------------------------------------------------------------------------------------------------------------------------------------------------------|
| 0 (0000000x) | Steady state flag. **0** - fan didn't (yet) reach the target speed. **1** - fan reached the requested target speed or fan running in constant duty mode. |
| 0 (0000000x) | Steady state flag. **0** - fan reached the requested target speed or fan running in constant duty mode. **1** - fan didn't (yet) reach the target speed. |
| 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 type: **0** - Fan controlled by PWM pin (4 pin plug), **this is the initial mode**. **1** - Fan controlled by DC voltage (3 pin plug) |
| 2 (00000x00) | Fan power disable: **0** - Fan operates normally. **1** - 12V supply to the fan is cut. |
# TODO:
- fan modes, states
- pwm/dc mode
| 1 (000000x0) | Fan power disable: **0** - Fan operates normally. **1** - 12V supply to the fan is cut. |

View File

@@ -6,12 +6,10 @@
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define CLAMP(min, max, val) MIN(MAX(min, val), max)
volatile uint16_t _fan_driver_delta[12];
volatile uint32_t _delta_idx = 0;
static const uint16_t pwm_timer_period = 2048;
static const uint8_t rpm_buf_size = sizeof(_fan_driver_delta) / sizeof(_fan_driver_delta[0]);
@@ -26,11 +24,6 @@ enum {
MODE_RPM_TARGET = 1
} fan_driver_control_mode;
enum {
FAN_TYPE_PWM = 0,
FAN_TYPE_DC = 1
} fan_driver_fan_type;
// callback for the pin interrupt
void TIM2_IRQHandler(void) __attribute__((interrupt));
@@ -48,10 +41,15 @@ void TIM2_IRQHandler(void) {
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_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));
GPIOC->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP) << (4 * power_pin);
// pwm stuff init
// port conf register is 32 bits. each pin is 4 bits
@@ -64,7 +62,7 @@ void fan_driver_init(void) {
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 = 0; // initial duty at 0
TIM1->CH1CVR = TIM1->ATRLR; // initial duty at full speed
TIM1->BDTR |= TIM_MOE; // enable outputs
TIM1->CTLR1 |= TIM_CEN; // enable timer
@@ -103,30 +101,35 @@ static void fan_driver_pwm_set_duty(const uint16_t duty) {
TIM1->CH1CVR = MIN(duty, fan_driver_pwm_get_max_duty());
}
static void fan_driver_set_power(const bool power) {
GPIOC->BSHR = (1 << (6 + 16 * !power));
}
void fan_driver_control_loop(void) {
// static uint16_t counter = 0;
static uint32_t last_delta_idx = 0xffffffff;
if(_delta_idx == last_delta_idx) {
fan_driver_is_stalled = (_delta_idx == last_delta_idx);
if(fan_driver_is_stalled) {
fan_driver_current_rpm = 0;
fan_driver_is_stalled = true;
return;
}
fan_driver_is_stalled = false;
last_delta_idx = _delta_idx;
fan_driver_set_power(!fan_driver_powered_down);
if (fan_driver_powered_down)
return;
uint32_t delta = 0;
for (int i = 0; i < rpm_buf_size; i++)
delta += _fan_driver_delta[i];
delta /= rpm_buf_size;
// each tick is 2us. one minute is 30000000 ticks, each rotation is 2 pulses
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 == MODE_RPM_TARGET) {
const int32_t duty_trim = (fan_driver_current_rpm - fan_driver_target_rpm) / 3;
fan_driver_pwm_set_duty(fan_driver_pwm_get_duty() + CLAMP(-100, 100, duty_trim));
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;

View File

@@ -31,7 +31,7 @@ static void i2c_registers_reinit(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, power enabled
reg[I2C_REG_MODE] = 0x01; // target rpm mode, pwm control, power enabled
}
#endif //I2C_H

View File

@@ -5,25 +5,31 @@
volatile uint8_t i2c_registers[16] = {0x00};
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_powered_down = (reg[I2C_REG_MODE] & 0x02) >> 1;
}
void onWrite(const uint8_t reg, const uint8_t length) {
if (length == 0) return;
switch (reg) {
case I2C_REG_TARGET_RPM:
fan_driver_target_rpm = i2c_get_register_word(i2c_registers, 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:
printf("set duty cycle to %d\n", i2c_get_register_word(i2c_registers, I2C_REG_CURRENT_DUTY));
update_fan_from_regs(i2c_registers);
printf("set duty cycle to %d\n", fan_driver_pwm_get_duty());
break;
case I2C_REG_MODE:
fan_driver_control_mode = (i2c_registers[I2C_REG_MODE] & 0x01);
fan_driver_fan_type = (i2c_registers[I2C_REG_MODE] & 0x02) >> 1;
fan_driver_powered_down = (i2c_registers[I2C_REG_MODE] & 0x04) >> 2;
printf("set mode register to %x. control mode is %d, type is %d, powered down is %d\n", i2c_registers[I2C_REG_MODE], fan_driver_control_mode, fan_driver_fan_type, fan_driver_powered_down);
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;
default:
@@ -39,7 +45,6 @@ int main() {
printf("System bootup\n");
fan_driver_init();
fan_driver_pwm_set_duty(1000);
// Initialize I2C slave
funGpioInitC();
@@ -47,6 +52,7 @@ int main() {
funPinMode(PC2, GPIO_CFGLR_OUT_10Mhz_AF_OD); // SCL
i2c_registers_reinit(i2c_registers);
i2c_registers_set_defaults(i2c_registers);
update_fan_from_regs(i2c_registers);
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);
@@ -56,9 +62,9 @@ int main() {
i2c_set_register_word(i2c_registers, I2C_REG_CURRENT_RPM, fan_driver_current_rpm);
i2c_set_register_word(i2c_registers, I2C_REG_CURRENT_DUTY, fan_driver_pwm_get_duty());
i2c_registers[I2C_REG_STATE] = (fan_driver_is_steady) | (fan_driver_is_stalled << 1);
i2c_registers[I2C_REG_STATE] = (!fan_driver_is_steady) | (fan_driver_is_stalled << 1);
Delay_Ms(200);
Delay_Ms(250);
}
}