/** * fan_motor_control.c * * Fan power control and speed sensing. * * Copyright (c) 2022 Bigscreen, Inc. */ #include #include "fan_motor_control.h" Fan_State_T fan_state; uint32_t fan_speed_hz; uint32_t fan_speed_rpm; void init_fan(void) { // Startup the Timer/Counter module sysclk_enable_peripheral_clock(ID_TC0); sysclk_enable_peripheral_clock(ID_TC1); // Set input / output pin modes ioport_set_pin_mode(PIN_FAN_PWM, PINMODE_FAN_PWM); ioport_disable_pin(PIN_FAN_PWM); ioport_set_pin_mode(PIN_FAN_FREQ, PINMODE_FAN_FREQ | IOPORT_MODE_PULLUP); ioport_disable_pin(PIN_FAN_FREQ); //pio_set_peripheral(FAN_PIO, PINMODE_FAN_FREQ, FAN_FG_PIN_MASK); //pio_set_peripheral(FAN_PIO, PINMODE_FAN_PWM, PIN_FAN_PWM); // Set pullup on FG pin. The fan's speed output is open drain //FAN_FG_PORT->PIO_PUER |= FAN_FG_PIN_MASK; // Enable PCK3 for 1MHz PMC->PMC_PCK[3] = PCK3_SRC + PCK3_DIV; // Main clock divided by 32 PMC->PMC_SCER = PMC_SCER_PCK3; // Enable PCK3 // Channel 0 used for counting (input) TC0->TC_CHANNEL[0].TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK5 // PCK3 (set to PLL0 / 98, about 1MHz) // Overflow will occur after 2^16 - 1 or 65535 counts, which is 262ms (about 1/4 second) + TC_CMR_ETRGEDG_FALLING // external trigger on falling edges + TC_CMR_ABETRG // external trigger is 'A' input + TC_CMR_LDRA_RISING // rising edges load the capture 'A' register + TC_CMR_LDRB_FALLING; // falling edges load the capture 'B' register (and resets, from ext. trigger) TC0->TC_CHANNEL[0].TC_IER = TC_IER_COVFS + TC_IER_LDRBS; // Interrupt after two captures (B was loaded, following A load) or overflow TC0->TC_CHANNEL[0].TC_CCR = TC_CCR_CLKEN + TC_CCR_SWTRG; // Enable the clock // Channel 1 used for PWM output // Upcounting mode, counts up to value of RC and then resets. // The frequency is then TimerClk / (RC+1) // Output B is set when counter is at zero (at RC match), and is reset // at RB match. This makes it easy to set PWM duty cycle. Duty cycle // is simply the value of RB / RC. TC0->TC_CHANNEL[1].TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK2 // MCK / 8 = 98.3MHz / 8 = 12.288MHz + TC_CMR_WAVE // Waveform output mode + TC_CMR_WAVSEL_UP_RC // Counts up to RC and then resets to zero, counting up again + TC_CMR_BCPB_CLEAR // Output B clears on RB match + TC_CMR_BCPC_CLEAR // Output B also clears on RC match. This gets changed to "set on RC match" whenever // duty cycle isn't zero. If it's on "set", the output is 100% high even at zero duty + TC_CMR_EEVT_XC0; // Anything except B as ext trig. Otherwise, B is set as input TC0->TC_CHANNEL[1].TC_RC = FAN_PWM_RC; TC0->TC_CHANNEL[1].TC_RB = 0; // Start with 0% duty TC0->TC_CHANNEL[1].TC_CCR = TC_CCR_CLKEN + TC_CCR_SWTRG; // Enable the clock fan_state = FAN_STOPPED; // Enable capture and overflow interrupts NVIC_EnableIRQ(TC0_IRQn); NVIC_SetPriority(TC0_IRQn, FAN_INTERRUPT_PRIO); // Lower priority than USB, that's all that matters probably. } void disable_fan(void) { NVIC_DisableIRQ(TC0_IRQn); ioport_set_pin_mode(PIN_FAN_PWM, 0); ioport_enable_pin(PIN_FAN_PWM); ioport_set_pin_dir(PIN_FAN_PWM, IOPORT_DIR_OUTPUT); ioport_set_pin_level(PIN_FAN_PWM, false); sysclk_disable_peripheral_clock(ID_TC0); sysclk_disable_peripheral_clock(ID_TC1); } void set_fan_pwm(uint16_t duty_cycle) { // Convert 0 to 100 duty cycle into timer counts if(duty_cycle > 100) { duty_cycle = 100; } uint32_t max_duty = TC0->TC_CHANNEL[1].TC_RC; uint32_t new_counts = duty_cycle*max_duty / 100; TC0->TC_CHANNEL[1].TC_RB = new_counts; if(duty_cycle == 0) { // Change Set on RC match to Clear on RC match // Otherwise the output is 100% high, and we don't want that TC0->TC_CHANNEL[1].TC_CMR &= ~(TC_CMR_BCPC_Msk); TC0->TC_CHANNEL[1].TC_CMR |= TC_CMR_BCPC_CLEAR; } else { // Change back to normal TC0->TC_CHANNEL[1].TC_CMR &= ~(TC_CMR_BCPC_Msk); TC0->TC_CHANNEL[1].TC_CMR |= TC_CMR_BCPC_SET; } } uint16_t get_fan_speed(void) { return (uint16_t) fan_speed_rpm; } void TC0_Handler(void) { // Get status register - this clears all interrupts, so we better service anything that was set uint32_t tcstatus = TC0->TC_CHANNEL[0].TC_SR; if((tcstatus & TC_SR_COVFS)!=0) { // Overflow occurred. Means fan is stopped for at least 1/4 second fan_state = FAN_STOPPED; fan_speed_hz = 0; fan_speed_rpm = 0; } if((tcstatus & TC_SR_LDRBS) != 0) { // Capture B happened, so we got a rising and falling edge in a row! Fan is spinnin fan_state = FAN_RUNNING; // Do the math on how fast it's going // RB is two captures in, a high period then a low period // This is 1/3 of a full fan rotation, according to our friends at Delta Electronics // BSB0205HA5-00HSF fan_speed_hz = FAN_FG_TIMERCLK / 3 / (TC0->TC_CHANNEL[0].TC_RB); fan_speed_rpm = fan_speed_hz * 60; } }