FEATIRE COMPLETE?
This commit is contained in:
39
README.md
39
README.md
@@ -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. |
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
24
src/main.c
24
src/main.c
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user