/* ATtiny13 1.2MHz */ #define F_CPU 1200000L #include #include #include // Match channels with outputs. #define OUTPUT_DDR DDRB #define OUTPUT_PORT_OUT PORTB #define OUTPUT_CHANNEL_A PB2 #define OUTPUT_CHANNEL_B_MAIN PB3 #define OUTPUT_CHANNEL_B_DIODES PB4 // Set input_0 RPM thresholds. Those thresholds will affect speed of fan(s) connected to the Channel A. During each rotation fan generates two pulses. // This device counts number of level changes during 100ms period, so each additional level change (four of them during one rotation) represent // increase in rotational speed of 150 RPM. #define A0_SPEED_0 3 // 450 RPM #define A0_SPEED_1 5 // 750 RPM #define A0_SPEED_2 6 // 900 RPM // Set input_1 RPM thresholds. Those thresholds will affect speed of fan(s) connected to the Channel A. #define A1_SPEED_0 6 // 900 RPM #define A1_SPEED_1 8 // 1200 RPM #define A1_SPEED_2 9 // 1350 RPM // Set input_1 RPM thresholds. Those thresholds will affect speed of fan(s) connected to the Channel B. #define B1_SPEED_0 2 // 300 RPM #define B1_SPEED_1 6 // 900 RPM #define B1_SPEED_2 7 // 1050 RPM #define B1_SPEED_3 8 // 1200 RPM #define B1_SPEED_4 9 // 1350 RPM volatile uint8_t frequency, sequence, sequences_count, input_0_counter, input_1_counter, channel_A_level, channel_B_level, temp_channel_B_level, channel_A0_level, channel_A1_level, channel_A0_level_measured, channel_A1_level_measured, channel_A0_lower_rpm_cycles, channel_A1_lower_rpm_cycles, channel_B1_level, channel_B1_level_measured, channel_B1_lower_rpm_cycles, channel_B_lowest_level_alterations; // This function will, after waiting 50ms for voltage levels to stabilize, set both output channels to maximum voltage for 0.5s, so that rotational momentum of fan's rotor can build up and any problems with starting fan at low voltage/power can be avoided. void start_up(void) { _delay_ms(50); OUTPUT_PORT_OUT |= (1 << OUTPUT_CHANNEL_A) | (1 << OUTPUT_CHANNEL_B_MAIN) | (1 << OUTPUT_CHANNEL_B_DIODES); _delay_ms(500); } // Use ADC to check if jumper closes FREQUENCY header pins connected to RESET/ADC0. void determine_frequency(void) { // Start analog-to-digital conversion. ADCSRA |= (1 << ADSC); // Wait for it to finish. while (ADCSRA & (1 << ADSC)); // When voltage on a RESET/ADC0 fall below a certain level, set frequency variable to 1, otherwise keep it 0. if(ADCH < 210) frequency = 1; // 30Hz else frequency = 0; // 10Hz } // Combine speed measurements from both input_0 and input_1 into levels that will be used to determine Channel A output voltage. void determine_channel_A_level(void) { // Determine temporary level for input_0 (channel_A0_level_measured). if(input_0_counter > A0_SPEED_2) channel_A0_level_measured = 3; else if(input_0_counter > A0_SPEED_1) channel_A0_level_measured = 2; else if(input_0_counter > A0_SPEED_0) channel_A0_level_measured = 1; else channel_A0_level_measured = 0; // Increase more permanent input_0 level (channel_A0_level) if temporary channel_A0_level_measured is larger than channel_A0_level. When // channel_A0_level_measured is lower than input_0 level, only increment channel_A0_lower_rpm_cycles (it may be used later to decrease A0 level, // waiting few 100ms cycles for that prevents frequent speed changes of output fan when input RPM value is at the border between two levels). In // cases when channel_A0_level_measured is equal or larger than channel_A0_level, reset channel_A0_lower_rpm_cycles so that counting toward actual // decrease of channel_A0_level is stopped. if(channel_A0_level_measured > channel_A0_level) { channel_A0_level = channel_A0_level_measured; channel_A0_lower_rpm_cycles = 0; } else if(channel_A0_level_measured < channel_A0_level) channel_A0_lower_rpm_cycles++; else channel_A0_lower_rpm_cycles = 0; // When channel_A0_lower_rpm_cycles counter increases above 2, lower channel_A0_level and reset counter value. if(channel_A0_lower_rpm_cycles > 2) { channel_A0_level--; channel_A0_lower_rpm_cycles = 0; } // Determine temporary level for input_1 (channel_A1_level_measured). if(input_1_counter > A1_SPEED_2) channel_A1_level_measured = 3; else if(input_1_counter > A1_SPEED_1) channel_A1_level_measured = 2; else if(input_1_counter > A1_SPEED_0) channel_A1_level_measured = 1; else channel_A1_level_measured = 0; // Increase more permanent input_1 level (channel_A1_level) if temporary channel_A1_level_measured is larger than channel_A1_level. When // channel_A1_level_measured is lower than input_1 level, only increment channel_A1_lower_rpm_cycles (it may be used later to decrease A1 level, // waiting few 100ms cycles for that prevents frequent speed changes of output fan when input RPM value is at the border between two levels). In // cases when channel_A1_level_measured is equal or larger than channel_A1_level, reset channel_A1_lower_rpm_cycles so that counting toward actual // decrease of channel_A1_level is stopped. if(channel_A1_level_measured > channel_A1_level) { channel_A1_level = channel_A1_level_measured; channel_A1_lower_rpm_cycles = 0; } else if(channel_A1_level_measured < channel_A1_level) channel_A1_lower_rpm_cycles++; else channel_A1_lower_rpm_cycles = 0; // When channel_A1_lower_rpm_cycles counter increases above 2, lower channel_1_level and reset counter value. if(channel_A1_lower_rpm_cycles > 2) { channel_A1_level--; channel_A1_lower_rpm_cycles = 0; } // Combine both A0 and A1 levels together and clip the resulting value when it exceeds 3 (it means that Channel A fans can achieve maximum RPM // even when only one of the inputs is active). Variable that controls PWM duty cycle must contain a value in 0-3 range (0%, 33%, 66%, 100%). channel_A_level = channel_A0_level + channel_A1_level; if(channel_A_level > 3) channel_A_level = 3; } // Use speed measurement from input_1 to generate levels that will be used to determine Channel B output voltage. void determine_channel_B_level(void) { // Determine temporary level for input_1 (channel_B1_level_measured). if(input_1_counter > B1_SPEED_4) channel_B1_level_measured = 6; else if(input_1_counter > B1_SPEED_3) channel_B1_level_measured = 5; else if(input_1_counter > B1_SPEED_2) channel_B1_level_measured = 4; else if(input_1_counter > B1_SPEED_1) channel_B1_level_measured = 3; else if(input_1_counter > B1_SPEED_0) channel_B1_level_measured = 2; else channel_B1_level_measured = 1; // Increase more permanent input_1 level (channel_B1_level) if temporary channel_B1_level_measured is larger than channel_B1_level. When // channel_B1_level_measured is lower than input_1 level, only increment channel_B1_lower_rpm_cycles (it may be used later to decrease B1 level, // waiting few 100ms cycles for that prevents frequent speed changes of output fan when input RPM value is at the border between two levels). In // cases when channel_B1_level_measured is equal or larger than channel_B1_level, reset channel_B1_lower_rpm_cycles so that counting toward actual // decrease of channel_B1_level is stopped. if(channel_B1_level_measured > channel_B1_level) { channel_B1_level = channel_B1_level_measured; channel_B1_lower_rpm_cycles = 0; } else if(channel_B1_level_measured < channel_B1_level) channel_B1_lower_rpm_cycles++; else channel_B1_lower_rpm_cycles = 0; // When channel_B1_lower_rpm_cycles counter increases above 2, lower channel_1_level and reset counter value. if(channel_B1_lower_rpm_cycles > 2) { channel_B1_level--; channel_B1_lower_rpm_cycles = 0; } // Copy value of channel_B1_level into main channel_B_level variable. channel_B_level = channel_B1_level; // Every time this function is called change state of channel_B_lowest_level_alterations between 0 and 1. When Channel B is at the lowest possible // level, add the channel_B_lowest_level_alterations to the channel_B_level so that PWM duty cycle changes between 33% and 66% (keeping fan PWM at // constant 33% when its voltage is limited by the diodes may stall it). channel_B_lowest_level_alterations ^= 0x01; if(channel_B_level == 1) channel_B_level += channel_B_lowest_level_alterations; // Channel B has two modes. In one of them, main MOSFET (Q2 connected to PB3) is always off and Q1 connected to PB4 turns on for longer or shorter // periods (fan voltage changes between 0V and ~8V). In second mode, diodes MOSFET (Q1 connected to PB4) is always on and Q2 connected top PB3 is // switched (fan voltage changes between ~8V and 12V). First mode is active when channel_B_level stays bellow or is equal to 3, second mode // activates when channel_B_level exceeds 3. Because variable that controls PWM duty cycle must contain a value in 0-3 range (0%, 33%, 66%, 100%), // temp_channel_B_level is generated. In first mode, temp_channel_B_level and channel_B_level are the same. In the second mode temp_channel_B_level // is channel_B_level subtracted by 3 (maximum level in the first mode). if(channel_B_level > 3) temp_channel_B_level = channel_B_level- 3; else temp_channel_B_level = channel_B_level; } // This function drives Channel A outputs. void advance_sequence_channel_A(void) { // Turn on Channel A transistor (Q3 connected to PB2)when sequence (0..2) is smaller than channel_A_level (0..3), so that fan(s) are driven by // higher voltage for 0%, 33%, 66% or 100% of 100ms (or 33ms) cycle. if(sequence < channel_A_level) OUTPUT_PORT_OUT |= (1 << OUTPUT_CHANNEL_A); else OUTPUT_PORT_OUT &= ~(1 << OUTPUT_CHANNEL_A); } // This function drives Channel B outputs. void advance_sequence_channel_B(void) { // Second mode with diodes MOSFET (Q1/PB4) always on, fan voltage changes between ~8V and 12V. if(channel_B_level > 3) { OUTPUT_PORT_OUT |= (1 << OUTPUT_CHANNEL_B_DIODES); // Turn on Channel B main transistor (Q2 connected to PB3) when sequence (0..2) is smaller than temp_channel_B_level (0..3), so that fan(s) are // driven by higher voltage for 0%, 33%, 66% or 100% of 100ms (or 33ms) cycle. if(sequence < temp_channel_B_level) OUTPUT_PORT_OUT |= (1 << OUTPUT_CHANNEL_B_MAIN); else OUTPUT_PORT_OUT &= ~(1 << OUTPUT_CHANNEL_B_MAIN); } // First mode with main MOSFET (Q2/PB3) always off, fan voltage changes between 0V and ~8V. else { OUTPUT_PORT_OUT &= ~(1 << OUTPUT_CHANNEL_B_MAIN); // Turn on Channel B diodes transistor (Q1 connected to PB4) when sequence (0..2) is smaller than temp_channel_B_level (0..3), so that fan(s) // are driven for 0%, 33%, 66% or 100% of 100ms (or 33ms) cycle. if(sequence < temp_channel_B_level) OUTPUT_PORT_OUT |= (1 << OUTPUT_CHANNEL_B_DIODES); else OUTPUT_PORT_OUT &= ~(1 << OUTPUT_CHANNEL_B_DIODES); } } int main(void) { // Set pins that are connected (indirectly) to the gates of MOSFETs as outputs . OUTPUT_DDR |= (1 << OUTPUT_CHANNEL_A) | (1 << OUTPUT_CHANNEL_B_MAIN) | (1 << OUTPUT_CHANNEL_B_DIODES); // At the device start, drive all the fans by highest possible voltage. start_up(); // Set up ADC, default ADC input is PB5/ADC0 // --------------------------------------------------------------------------------------------------------------- // Left adjust result. ADMUX |= (1 << ADLAR); // Prescaler to 16, enable ADC. ADCSRA |= (1 << ADPS2) | (1 << ADEN); // --------------------------------------------------------------------------------------------------------------- // Set up external interrupts that will be used to measure RPM values of input fans. // --------------------------------------------------------------------------------------------------------------- // Enable PCINT0 (input_0). PCMSK |= (1<2) sequences_count = 0; } else { determine_channel_A_level(); determine_channel_B_level(); input_0_counter = 0; input_1_counter = 0; } } // --------------------------------------------------------------------------------------------------------------- // Set outputs for the correct states in this step of the sequence (and calculated channel levels). advance_sequence_channel_A(); advance_sequence_channel_B(); // Wait for step to end. It will take more or less time, depending on selected frequency. if(frequency) _delay_ms(11); else _delay_ms(33); // Increase sequence step count by one. If step count exceeds 2, set it to 0 and start PWM cycle again. sequence++; if(sequence>2) sequence=0; } } // When PCINT0 (change of voltage level on PB0) interrupt occurs (4 of them should ocur during one input fan rotation), increment input_0_counter. ISR(PCINT0_vect) { input_0_counter++; } // When INT0 (change of voltage level on PB1) interrupt occurs (4 of them should ocur during one input fan rotation), increment input_1_counter. ISR(INT0_vect) { input_1_counter++; }