/** * Fan controller. * AN0 - Potentiometer input (0-5V). * PWM1 - Motor speed control output. * T1G - Motor tachometer signal input (4-42Hz pulses). * PWM3 - Tachometer panel meter output. */ #include "mcc_generated_files/mcc.h" // This is 100% for the motor PWM output (PWM1PRH, PWM1PRL). #define FAN_DUTY_MAX (255) // Should be set higher than the minimum starting speed. #define FAN_DUTY_MIN (38) // Full-scale deflection on the tachometer. #define TACH_HZ_MAX (50) // 100% duty cycle for the tachometer PWM output (PWM3PRH, PWM3PRL). #define TACH_DUTY_MAX (255) // Timer1 is set to use LFINTOSC, so this is simply that clock speed (31kHz). #define TMR1_COUNTS_PER_SECOND (31000) /** * Set PWM to control motor speed from potentiometer position. */ void setSpeedFromPot() { adc_result_t potPosition; uint16_t pwmDutyCycle; ADC1_StartConversion(); potPosition = ADC1_GetConversionResult(); pwmDutyCycle = (potPosition >> 2) + FAN_DUTY_MIN; if (pwmDutyCycle > FAN_DUTY_MAX) { pwmDutyCycle = FAN_DUTY_MAX; } PWM1_DutyCycleSet(pwmDutyCycle); PWM1_LoadBufferSet(); } // The moving coil meter doesn't respond linearly with input voltage. // Apply the inverse of that response to the PWM output so it appears linear. uint16_t applyMeterCorrection(uint32_t x) { uint16_t x3 = (x * x * x) / 71430; uint16_t x2 = (x * x) / 278; return (x3 - x2) + x; } /** * The tachometer output from the motor is one pulse per revolution. * Since its duty cycle is 50%, the RPM can be determined from the width of a single pulse. * Use that to set a PWM output that is sent to a panel meter. */ void setTachFromTach() { uint16_t pulseWidthCounts; uint32_t pwmDutyCycle; if (TMR0_HasOverflowOccured()) { // TMR0 sets an upper limit on how long to wait for a pulse. If one hasn't occurred by then, the speed is probably 0. PWM3_DutyCycleSet(0); PWM3_LoadBufferSet(); TMR0_Reload(); INTCONbits.TMR0IF = 0; } else if (TMR1_HasOverflowOccured()) { // The fan shouldn't normally be able to spin this slow, but just in case, treat it as 0. PWM3_DutyCycleSet(0); PWM3_LoadBufferSet(); TMR1_StopTimer(); T1GCONbits.T1GGO = 0; PIR1bits.TMR1IF = 0; TMR1_Reload(); TMR1_StartTimer(); TMR1_StartSinglePulseAcquisition(); TMR0_Reload(); INTCONbits.TMR0IF = 0; } else if(T1GCONbits.T1GGO == 0) { pulseWidthCounts = TMR1_ReadTimer(); pwmDutyCycle = TMR1_COUNTS_PER_SECOND; pwmDutyCycle *= TACH_DUTY_MAX; pwmDutyCycle /= TACH_HZ_MAX; pwmDutyCycle /= 2; // Divide by 2 because the pulse width is only half a cycle. pwmDutyCycle /= pulseWidthCounts; PWM3_DutyCycleSet(applyMeterCorrection(pwmDutyCycle)); PWM3_LoadBufferSet(); TMR1_Reload(); TMR1_StartTimer(); TMR1_StartSinglePulseAcquisition(); TMR0_Reload(); INTCONbits.TMR0IF = 0; } } void main(void) { SYSTEM_Initialize(); PWM1_Start(); PWM3_Start(); TMR1_StartTimer(); TMR1_StartSinglePulseAcquisition(); while (1) { setSpeedFromPot(); setTachFromTach(); } }