actual project

This commit is contained in:
2026-04-12 20:11:17 +02:00
parent 4ebf35f285
commit 82e43b67da
11 changed files with 590 additions and 75 deletions

137
src/fan_driver.h Normal file
View File

@@ -0,0 +1,137 @@
#ifndef FAN_DRIVER_H
#define FAN_DRIVER_H
#include <stdio.h>
#include "ch32fun.h"
#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]);
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;
enum {
FAN_TYPE_PWM = 0,
FAN_TYPE_DC = 1
} fan_driver_fan_type;
// 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) {
const uint16_t capture = TIM2->CH2CVR;
_fan_driver_delta[_delta_idx % rpm_buf_size] = capture - _fan_driver_last_capture;
_delta_idx++;
_fan_driver_last_capture = capture;
TIM2->INTFR &= ~TIM_IT_CC2; // clear bit
}
}
void fan_driver_init(void) {
const uint8_t pwm_pin = 2; // also need to change TIM1->CCER
const uint8_t tacho_pin = 3;
// 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
// pwm stuff init
// port conf register is 32 bits. each pin is 4 bits
// so for pd2 we skip pd0, pd1. 4 bits * 2 pins
// 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->CHCTLR1 |= TIM_OC1M_2 | TIM_OC1M_1; // set compare mode, set output?
TIM1->CH1CVR = 0; // initial duty at 0
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
// 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->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
// enable interrupt, enable timer
NVIC->IENR[((uint32_t)(TIM2_IRQn) >> 5)] = (1 << ((uint32_t)(TIM2_IRQn) & 0x1F)); // NVIC_EnableIRQ(TIM2_IRQn);
TIM2->CTLR1 |= TIM_CEN; // enable timer
}
static uint16_t fan_driver_pwm_get_max_duty(void) {
return (pwm_timer_period - 1);
}
static uint16_t fan_driver_pwm_get_duty(void) {
return MIN(TIM1->CH1CVR, fan_driver_pwm_get_max_duty());
}
static void fan_driver_pwm_set_duty(const uint16_t duty) {
TIM1->CH1CVR = MIN(duty, fan_driver_pwm_get_max_duty());
}
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_current_rpm = 0;
fan_driver_is_stalled = true;
return;
}
fan_driver_is_stalled = false;
last_delta_idx = _delta_idx;
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;
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));
fan_driver_is_steady = (duty_trim == 0);
} else {
fan_driver_is_steady = true;
}
}
#endif //FAN_DRIVER_H

7
src/funconfig.h Normal file
View File

@@ -0,0 +1,7 @@
#ifndef FUNCONFIG_H
#define FUNCONFIG_H
#define FUNCONF_USE_DEBUGPRINTF 0
#define FUNCONF_USE_UARTPRINTF 1
#endif //FUNCONFIG_H

37
src/i2c.h Normal file
View File

@@ -0,0 +1,37 @@
/* Bunch of i2c helper functions and defs mostly */
#ifndef I2C_H
#define I2C_H
#define I2C_DEVICE_ADDR 0x2b
#define I2C_REG_WHOAMI 0x00
#define I2C_REG_CURRENT_RPM 0x02
#define I2C_REG_TARGET_RPM 0x04
#define I2C_REG_CURRENT_DUTY 0x06
#define I2C_REG_MAX_DUTY 0x08
#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) {
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) {
if (addr % 2) return 0;
return (reg[addr] << 8) + reg[addr + 1];
}
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) {
i2c_set_register_word(reg, I2C_REG_TARGET_RPM, 750);
reg[I2C_REG_MODE] = 0x01; // target rpm mode, power enabled
}
#endif //I2C_H

221
src/i2c_driver.h Normal file
View File

@@ -0,0 +1,221 @@
/*
* Single-File-Header for using the I2C peripheral in slave mode
*
* MIT License
*
* Copyright (c) 2024 Renze Nicolai
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef __I2C_SLAVE_H
#define __I2C_SLAVE_H
#include "ch32fun.h"
#include <stdint.h>
#include <stdio.h>
#include <stdbool.h>
typedef void (*i2c_write_callback_t)(uint8_t reg, uint8_t length);
typedef void (*i2c_read_callback_t)(uint8_t reg);
struct _i2c_slave_state {
uint8_t first_write;
uint8_t offset;
uint8_t position;
volatile uint8_t* volatile registers1;
volatile uint8_t size1;
volatile uint8_t* volatile registers2;
volatile uint8_t size2;
i2c_write_callback_t write_callback1;
i2c_read_callback_t read_callback1;
bool read_only1;
i2c_write_callback_t write_callback2;
i2c_read_callback_t read_callback2;
bool read_only2;
bool writing;
bool address2matched;
} i2c_slave_state;
void SetupI2CSlave(uint8_t address, volatile uint8_t* registers, uint8_t size, i2c_write_callback_t write_callback, i2c_read_callback_t read_callback, bool read_only) {
i2c_slave_state.first_write = 1;
i2c_slave_state.offset = 0;
i2c_slave_state.position = 0;
i2c_slave_state.registers1 = registers;
i2c_slave_state.size1 = size;
i2c_slave_state.registers2 = NULL;
i2c_slave_state.size2 = 0;
i2c_slave_state.write_callback1 = write_callback;
i2c_slave_state.read_callback1 = read_callback;
i2c_slave_state.read_only1 = read_only;
i2c_slave_state.write_callback2 = NULL;
i2c_slave_state.read_callback2 = NULL;
i2c_slave_state.read_only2 = false;
// Enable I2C1
RCC->APB1PCENR |= RCC_APB1Periph_I2C1;
// Reset I2C1 to init all regs
RCC->APB1PRSTR |= RCC_APB1Periph_I2C1;
RCC->APB1PRSTR &= ~RCC_APB1Periph_I2C1;
I2C1->CTLR1 |= I2C_CTLR1_SWRST;
I2C1->CTLR1 &= ~I2C_CTLR1_SWRST;
// Set module clock frequency
uint32_t prerate = 2000000; // I2C Logic clock rate, must be higher than the bus clock rate
I2C1->CTLR2 |= (FUNCONF_SYSTEM_CORE_CLOCK/prerate) & I2C_CTLR2_FREQ;
// Enable interrupts
I2C1->CTLR2 |= I2C_CTLR2_ITBUFEN | I2C_CTLR2_ITEVTEN | I2C_CTLR2_ITERREN;
NVIC_EnableIRQ(I2C1_EV_IRQn); // Event interrupt
NVIC_SetPriority(I2C1_EV_IRQn, 2 << 4);
NVIC_EnableIRQ(I2C1_ER_IRQn); // Error interrupt
NVIC_SetPriority(I2C1_ER_IRQn, 2 << 4);
// Set clock configuration
uint32_t clockrate = 1000000; // I2C Bus clock rate, must be lower than the logic clock rate
I2C1->CKCFGR = ((FUNCONF_SYSTEM_CORE_CLOCK/(3*clockrate))&I2C_CKCFGR_CCR) | I2C_CKCFGR_FS; // Fast mode 33% duty cycle
//I2C1->CKCFGR = ((FUNCONF_SYSTEM_CORE_CLOCK/(25*clockrate))&I2C_CKCFGR_CCR) | I2C_CKCFGR_DUTY | I2C_CKCFGR_FS; // Fast mode 36% duty cycle
//I2C1->CKCFGR = (FUNCONF_SYSTEM_CORE_CLOCK/(2*clockrate))&I2C_CKCFGR_CCR; // Standard mode good to 100kHz
// Set I2C address
I2C1->OADDR1 = address << 1;
I2C1->OADDR2 = 0;
// Enable I2C
I2C1->CTLR1 |= I2C_CTLR1_PE;
// Acknowledge bytes when they are received
I2C1->CTLR1 |= I2C_CTLR1_ACK;
}
void SetupSecondaryI2CSlave(uint8_t address, volatile uint8_t* registers, uint8_t size, i2c_write_callback_t write_callback, i2c_read_callback_t read_callback, bool read_only) {
if (address > 0) {
I2C1->OADDR2 = (address << 1) | 1;
i2c_slave_state.registers2 = registers;
i2c_slave_state.size2 = size;
i2c_slave_state.write_callback2 = write_callback;
i2c_slave_state.read_callback2 = read_callback;
i2c_slave_state.read_only2 = read_only;
} else {
I2C1->OADDR2 = 0;
}
}
void SetI2CSlaveReadOnly(bool read_only) {
i2c_slave_state.read_only1 = read_only;
}
void SetSecondaryI2CSlaveReadOnly(bool read_only) {
i2c_slave_state.read_only2 = read_only;
}
void I2C1_EV_IRQHandler(void) __attribute__((interrupt));
void I2C1_EV_IRQHandler(void) {
uint16_t STAR1, STAR2 __attribute__((unused));
STAR1 = I2C1->STAR1;
STAR2 = I2C1->STAR2;
if (STAR1 & I2C_STAR1_ADDR) { // Start event
i2c_slave_state.first_write = 1; // Next write will be the offset
i2c_slave_state.position = i2c_slave_state.offset; // Reset position
i2c_slave_state.address2matched = !!(STAR2 & I2C_STAR2_DUALF);
}
if (STAR1 & I2C_STAR1_RXNE) { // Write event
if (i2c_slave_state.first_write) { // First byte written, set the offset
i2c_slave_state.offset = I2C1->DATAR;
i2c_slave_state.position = i2c_slave_state.offset;
i2c_slave_state.first_write = 0;
i2c_slave_state.writing = false;
} else { // Normal register write
i2c_slave_state.writing = true;
if (i2c_slave_state.address2matched) {
if (i2c_slave_state.position < i2c_slave_state.size2 && !i2c_slave_state.read_only2) {
i2c_slave_state.registers2[i2c_slave_state.position] = I2C1->DATAR;
i2c_slave_state.position++;
}
} else {
if (i2c_slave_state.position < i2c_slave_state.size1 && !i2c_slave_state.read_only1) {
i2c_slave_state.registers1[i2c_slave_state.position] = I2C1->DATAR;
i2c_slave_state.position++;
}
}
}
}
if (STAR1 & I2C_STAR1_TXE) { // Read event
i2c_slave_state.writing = false;
if (i2c_slave_state.address2matched) {
if (i2c_slave_state.position < i2c_slave_state.size2) {
I2C1->DATAR = i2c_slave_state.registers2[i2c_slave_state.position];
if (i2c_slave_state.read_callback2 != NULL) {
i2c_slave_state.read_callback2(i2c_slave_state.position);
}
i2c_slave_state.position++;
} else {
I2C1->DATAR = 0x00;
}
} else {
if (i2c_slave_state.position < i2c_slave_state.size1) {
I2C1->DATAR = i2c_slave_state.registers1[i2c_slave_state.position];
if (i2c_slave_state.read_callback1 != NULL) {
i2c_slave_state.read_callback1(i2c_slave_state.position);
}
i2c_slave_state.position++;
} else {
I2C1->DATAR = 0x00;
}
}
}
if (STAR1 & I2C_STAR1_STOPF) { // Stop event
I2C1->CTLR1 &= ~(I2C_CTLR1_STOP); // Clear stop
if (i2c_slave_state.address2matched) {
if (i2c_slave_state.write_callback2 != NULL) {
i2c_slave_state.write_callback2(i2c_slave_state.offset, i2c_slave_state.position - i2c_slave_state.offset);
}
} else {
if (i2c_slave_state.write_callback1 != NULL) {
i2c_slave_state.write_callback1(i2c_slave_state.offset, i2c_slave_state.position - i2c_slave_state.offset);
}
}
}
}
void I2C1_ER_IRQHandler(void) __attribute__((interrupt));
void I2C1_ER_IRQHandler(void) {
uint16_t STAR1 = I2C1->STAR1;
if (STAR1 & I2C_STAR1_BERR) { // Bus error
I2C1->STAR1 &= ~(I2C_STAR1_BERR); // Clear error
}
if (STAR1 & I2C_STAR1_ARLO) { // Arbitration lost error
I2C1->STAR1 &= ~(I2C_STAR1_ARLO); // Clear error
}
if (STAR1 & I2C_STAR1_AF) { // Acknowledge failure
I2C1->STAR1 &= ~(I2C_STAR1_AF); // Clear error
}
}
#endif

64
src/main.c Normal file
View File

@@ -0,0 +1,64 @@
#include "ch32fun.h"
#include "i2c_driver.h"
#include "fan_driver.h"
#include "i2c.h"
volatile uint8_t i2c_registers[16] = {0x00};
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);
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));
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);
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();
fan_driver_pwm_set_duty(1000);
// Initialize I2C slave
funGpioInitC();
funPinMode(PC1, GPIO_CFGLR_OUT_10Mhz_AF_OD); // SDA
funPinMode(PC2, GPIO_CFGLR_OUT_10Mhz_AF_OD); // SCL
i2c_registers_reinit(i2c_registers);
i2c_registers_set_defaults(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);
while (1) {
fan_driver_control_loop();
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);
Delay_Ms(200);
}
}