Jump to content

Hardware Fix for the Nvidia Kepler Series GPU Minimum 1000 RPM Fanspeed

Hi, I like quiet computers.

 

Let's get started.

 

First off: yes, I am aware that I am talking about a five years old GPU series but I found this interesting enough to share, so bear with me.

 

 

The problem:

As far as I am aware at least the 770, 780 & 780ti of Nvidia's GTX GPU series limit the minimum speed of the cooling fans to about 1000 RPM regardless of ambient conditions and software targets. This leads to unnecessary noise and wear in idle and low utilization.

 

The goal:

The minimum fanspeed is adjustable without impacting cooling capabilities under high load.

 

Theory:

A PC fan typically has three connections: ground, supply voltage & tacho signal. The first two supply power to the fan and by varying the voltage potential most fans will change their speed. The third indicates the speed of the the motor with multiple voltage pulses per revolution.

Often there is a fourth connection called PWM (Pulse Width Modulation) which can be used to adjust the RPM while keeping a constant supply voltage.

 

The mentioned Nvidia cards use four pin fans and - by detecting the fan speed at the third tacho connection - know when to maintain the appropriate PWM signal on the fourth pin to limit the minimum RPM.

Since there's no getting into the BIOS easily without risking the card's life the tacho signal the card receives must be manipulated to allow lower fan speed.

 

Concept:

The fans themselves aren't easy to modify so an external device will be implemented - ideally allowing for swapping fans without modifications.

This device will measure the fan's tacho signal, multiply it's frequency by an arbitrary number chosen by the user and output the modified signal with the resulting frequency.

 

Implementation:

An Arduino Nano will serve as the computing platform with supporting circuitry.

 

Code: Please, do not redistribute without my explicit written permission! You are free to use this code as you please for any civil non-profit application.

The code implementation in the Arduino IDE looks as follows:

/*
 * (c) 2018 - Karikiro
 * 
 * Do not remove this header. Do not redistribute. Not for use in commercial or military applications.
 * 
 * This Code is provided as is and may contain errors.
 * You are applying it at your own risk.
 * I take no responsibility for any damage or injuries caused by it.
*/

#define PIN_F_IN 2 //pin number of the input signal
#define PIN_F_OUT 9 //pin number of the output signal
#define F_SYS 16000000 //system clock frequency in Hz
#define UPDATE_TIME 500 //time delay in ms in which the input and output frequency is calculated and output frequency set
#define AVERAGE_TIME 2000 //time over which the mean of the input frequency is calculated; this value MUST BE a multiple of UPDATE_TIME
#define FREQUENCY_RATIO 2 //desired output frequency divided by input frequency

unsigned long previous_timestamp = 0; //last stored timestamp in ms
unsigned int icr1_val = 65535; //auxiliary variable to help dynamicly changing the Timer 1 frequency
unsigned int f_in_count = 0; //current number of pulses of f_in since last reset
unsigned int f_in_idx = 0; //array index for running average of f_in
float f_in[AVERAGE_TIME / UPDATE_TIME] = {}; //measured input frequency in Hz
float f_in_avg = 1;
float f_out = 1; //output frequency in Hz


void setupPins(){
  pinMode(PIN_F_IN, INPUT);
  pinMode(PIN_F_OUT, OUTPUT);
  attachInterrupt(digitalPinToInterrupt(PIN_F_IN), isrD2, FALLING); //attach the interrupt service routine 'isrD2' if 'PIN_F_IN' detects a FALLING edge
}


void setupTimer1(){ //Timer 1 will be responsible for providing the output frequency and will be configured as described in chapter 20.12.5 and shown in figure 20-9 on pages 166...168 of the datasheet.
  //Set Compare Output Mode (COM1xn) (P.171, T.20-5): Clear OC1A/OC1B on Compare Match when up-counting. Set OC1A/OC1B on Compare Match when down-counting.
  //Set Waveform Generation Mode (WGM1n) (P.171/172, T.20-6): Mode: 8 | Timer/Counter Mode of Operation: PWM, Phase and Frequency Correct | TOP: ICR1 | Update of OCR1x at: BOTTOM | TOV1 Flag Set on: BOTTOM
  //Set Prescaler (CS1n) (P.173, T.20-7): Prescaler is 64, therefore counting occurs every 64th I/O-clock
  TCCR1A |= _BV(COM1A1) | _BV(COM1B1);
  TCCR1A &= ~(_BV(COM1B0) | _BV(COM1A0) | _BV(WGM11) | _BV(WGM10));
  TCCR1B |= _BV(WGM13) | _BV(CS11) | _BV(CS10);
  TCCR1B &= ~(_BV(WGM12) | _BV(CS12));
  //'_BV(X)' is a MACRO and defined as '1 << X'. It shifts a '1' by 'X' places to the MSB (most significant Bit)
}


void setup(){
  setupPins();
  setupTimer1();
}


void isrD2(){
  f_in_count++;
}


void loop(){
  if ((millis() - previous_timestamp) >= UPDATE_TIME || (millis() - previous_timestamp) < 0){
    
    //Store Current Timestamp for Next Iteration
    previous_timestamp = millis();
    
    //Calculate Current Input Frequency
    f_in[f_in_idx] = 1000.0 * f_in_count / (float)(UPDATE_TIME); //multiplying by 1000 to convert from ms of UPDATE_TIME to s
    
    //Reset Counter
    f_in_count = 0;
    
    //Shift Index for Array of Input Frequencies
    f_in_idx++;
    if (f_in_idx >= (AVERAGE_TIME / UPDATE_TIME)) f_in_idx = 0;
    
    //Calculate Average of Input Frequencies
    f_in_avg = 0;
    for (int i = 0; i < (AVERAGE_TIME / UPDATE_TIME); i++) f_in_avg += f_in[i];
    f_in_avg /= (float)(AVERAGE_TIME / UPDATE_TIME);
    f_out = f_in_avg * (float)(FREQUENCY_RATIO);
    
    //Adapt Timer 1
    noInterrupts(); //disable interrupts to ensure correct register values
    icr1_val = (unsigned int)((float)(F_SYS) / (128 * f_out)); //defines the PWM frequency of the output frequency and is calculated with: f_PWM = F_SYS / (2 * prescaler * TOP) with TOP being ICR1 => ICR1 = F_SYS / (2 * prescaler * f_PWM)
    ICR1 = icr1_val;
    OCR1A = icr1_val / 2; //defines the PWM duty cycle
    interrupts(); //enable interrupts again
  }
}

 

Pin 2 (D2) will be used to measure the input tacho signal of the fan because of it's interrupt capabilities. Pin 9 (D9) will with supporting circuitry provide the graphics card with the modified frequency because of it can be mapped to a timer.

 

To allow for different Arduino platforms (those using Atmel's Atmega 328P micro controller) the system clock can be specified. Further, the interval time between updates of the output frequency can be set as well as an averaging interval time to suppress noise on the signal (which MUST BE a whole number multiple of the update time).

 

Timer 1 will later provide the actual output signal via pin 9 and is set up accordingly as an frequency adjustable PWM timer.

 

Now if a falling edge of the input tacho signal on the interrupt pin 2 (D2) occurs a counter is incremented immediately. (Exception: While the output time (Timer 1) is beeing configured interrupts are disabled.)

Else the main loop() is run indefinitely: Using the Arduino's millis() function the following is executed periodically (this implementation bugs out after about 49,7 days with a single inconsistent interval but this mod really isn't meant to operate for anywhere near that long and the averaging will smooth that out anyway):

The current millis() is stored for the next interval then the input tacho signal frequency is calculated using the interrupt counter of pin 2 (D2) which is reset right after and stored in an array containing some (in this exact code 4) previous values which are averaged and then fed into the timer 1 registers. At last the frequency counter is reset.

 

 

Circuitry:

To enable the Arduino Nano to measure and output the signals the following circuit is build:

 

Spoiler

Schematic_Frequency-Multiplier-compact_Arduino-Nano-Schematic-compact_20180806154958.png.14f2f0c0be188a2c8fd648c7f80a8002.png

 

PCB_Frequency-Multiplier-compact_Arduino-Nano-Schematic-compact_20180806151222.png.f1646ad8851ee5703fc0ae5e512a54eb.png

 

IMG_4886.thumb.png.3a1ee45f9d92a8e19a33274d3f11a7f1.png

 

The Arduino Nano is attached to a striped prototyping PCB with a bit of double sided sticky tape and connected with jumper cables.

 

The PCB is home to three four pin male pin headers for three fans, one four pin female pin header for the Arduino Nano, a transistor (Q1) with base resistor (RQ1), a pull up resistor (RZ1), a Zener diode (DZ) and a regular diode (DFW) (depicted as Schottky).

 

Q1 is driven by pin 9 (D9) of the Arduino Nano via RQ1 and provides the open drain (collector) type output tacho signal to the graphics card.

RZ1 is the pullup resistor for the input tacho signal of the fan which provides an open drain type signal. Since the Arduino Nano I am using is a 16MHz @ 5V variant DZ (a 5,1V Zener diode) clamps the signal voltage while the fan's tacho connection is floating.

Lastly DFW is used as a free wheeling diode for fan spin down to protect the power supply from backfeeding. (probably not necessary but nice to have)

 

 

Results:

Measuring with an oscilloscope the following traces can be observed:

 

Input Signal

Spoiler

IMG_4887.thumb.png.84251e83b3252f8e8aee49dc1caa6e07.png

 

Output Signal

Spoiler

IMG_4888.thumb.png.b43a51d09e3f94b7e71360a492cc61fd.png

 

Success! As is clearly visible (figuratively not literally because the pictures are pretty bad) the input frequency as shown in the top left corner of the TFT has doubled (for the provided code where FREQUENCY_RATIO was set to 2). This measured result is consistent with what MSI Afterburner reports at 100% fan speed target.

 

MSI Afterburner without mod

Spoiler

5b688a04a9b80_withoutmod.png.b4cac8e44eff43366f8f03bc468ee520.png

 

MSI Afterburner with mod

Spoiler

5b688a0e0e8d7_withmodx2.png.fa00341e5a786a950b805818416b5d93.png

The 500ms update interval is clearly visible. Also for some reason the fall time of the unmodified fan control is considerably longer than with the mod.

 

 

Conclusion:

It works. Would be nice to change the ratio externally but I can't be bothered.

If you have any questions please just ask I'll gladly answer them. (Though I may take some time to respond - I am not exactly active in this site but I'll try.)

Hope you enjoyed. ❤️

 

Reduce > Reuse > Recycle

 

Build-log (way out of date)

Link to comment
Share on other sites

Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×