Go Down

Topic: Controlling 4-pin computer fans - PWM at 25khz? (Read 75127 times) previous topic - next topic

Siftah

Hello,

I've been trying to control a 4-pin computer fan using the arduino. These fans take a PWM signal on their 4th pin to control the speed of the fan, from the spec's of the fan it says it requires/expects:
Code: [Select]

The following requirements are measured at the PWM (control) pin of the fan cable connector:
PWM Frequency: Target frequency 25 kHz, acceptable operational range 21 kHz to 28 kHz
Maximum voltage for logic low: VIL = 0.8 V
Absolute maximum current sourced: Imax = 5 mA (short circuit current)
Absolute maximum voltage level: VMax = 5.25 V (open circuit voltage)


Can the Arduino output a 25khz PWM signal? From what I've been able to google it looks like it only outputs 30khz, can anyone comment?

I've had limited success with my (very basic) code so far, any help would be gratefully received :)


MikMo

I'm quite sure you can change the PWM frequency on Arduino.

It has been discussed in the forum before, so try to serach the forum for "PWM frequency"

Siftah

Quote
I'm quite sure you can change the PWM frequency on Arduino.

It has been discussed in the forum before, so try to serach the forum for "PWM frequency"


Hmm, well I found "bens" posts, of which this one seems to be the most relevant: http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1212690065/5#5

I must admit this is on the edge of my comprehension, I can see that in the following code I need to change the OCR2A and TCCR2B values so set the prescaler and TOP value in order to get 25Mhz, however, I can't follow the calculation in order to work out what I should be putting into the register/variable.

Code: [Select]

void setup()
{
 // configure hardware timer2 to generate a fast PWM on OC2B (Arduino digital pin 3)
 // set pin high on overflow, clear on compare match with OCR2B
 TCCR2A = 0x23;
 TCCR2B = 0x0C;  // select timer2 clock as 16 MHz I/O clock / 64 = 250 kHz
 OCR2A = 249;  // top/overflow value is 249 => produces a 1000 Hz PWM
 pinMode(3, OUTPUT);  // enable the PWM output (you now have a PWM signal on digital pin 3)
 OCR2B = 125;  // set the PWM to 50% duty cycle
}


if 249 = 1kHz, I can also see that 62 = 12 kHz from another post.

Erk, Head hurts  ::)

bens

#3
Jul 10, 2008, 09:36 pm Last Edit: Jul 10, 2008, 09:37 pm by bens Reason: 1
Fast PWM frequency is given by the following equation:

timer frequency / (counter TOP + 1)

If I have a timer being clocked at 1 MHz and the timer counts from 0 to 99 before overflowing, my generated PWM will be equal to:

1 MHz / 100 = 10 kHz

timer frequency is determined by the selected prescaler, where:

timer frequency = system clock / prescaler

You can control the prescaler by using the TCCRxB register (x is the number of the timer you are using, and will be either 0 or 2 for an 8-bit timer or 1 for a 16-bit timer).  See the datasheet for information on how this register affects the timer prescaler.  You can control the counter TOP by setting the OCRxA register to the value you desire.

If you want a 25 kHz PWM and your Arduino is running at 16 MHz, the equation you need to solve is:

16 MHz / (prescaler * (TOP + 1)) = 25 kHz
=> prescaler * (TOP + 1) = 16 MHz / 25 kHz = 640

If we select a prescaler of 8, we get that:

TOP + 1 = 640 / 8 = 80
=> TOP = 79

So, to summarize, if you are running your Arduino at 16 MHz, you can use an 8-bit timer (e.g. timer 2) to generate a 25 kHz PWM by using a prescaler of 8 (i.e. TCCR2B = 0x09) and a TOP of 79 (i.e. OCR2A = 79).  Note that this won't give you a very high-resolution PWM as your duty cycle will have a resolution of 1/80 = 1.25%.  If this is an acceptable resolution, great.  If not, you can get a higher resolution by using the 16-bit timer1 with a prescaler of 1 and TOP of 639.

Does this make sense?

- Ben

hmd

Hi Ben

thanks for your clear topic of how to calibrate the timer.
i 'm looking the way to use all pwm available(6) of the arduino at the same time and at same frequency (20-25khz)
your example for pin3 is working but how to do same thing with the other pin?

i had a look of wiring_analog.c and pins_arduino.c it seems pin3(pwm) is on TIMER2B and i expect of TIMER2A, why?
you seems to know more than me about the timer and pwm, please can you tell me which pin are related to which timer, counter(tccrxx) and output compare(top)?

many thanks,

hmd  

Siftah

Quote

Does this make sense?

- Ben


It does Ben, *thankyou*, that pulled it together for me, I was sort of there with the information from your other posts but I'd not quite got my head round the equation until reading this!

Cheers! :)

bens

Quote
i had a look of wiring_analog.c and pins_arduino.c it seems pin3(pwm) is on TIMER2B and i expect of TIMER2A, why?
you seems to know more than me about the timer and pwm, please can you tell me which pin are related to which timer, counter(tccrxx) and output compare(top)?


All of this information is in the datasheet, which you'll probably want to reference if you want to achieve custom control of the timer hardware:

pin 3 = OC2B (timer 2 PWM output B)
pin 11 = OC2A (timer 2 PWM output A)
pin 9 = OC1B (timer 1 PWM output B)
pin 10 = OC1A (timer 1 PWM output A)
pin 5 = OC0B (timer 0 PWM output B)
pin 6 = OC0A (timer 0 PWM output A)

Note that information I gave earlier in this thread will not work for outputing a PWM on both of timer 0's or timer 2's PWM outputs.  The reason for this is that in order to control the TOP value of timers 0 and 2, you need to use the output compare register A (the register that you would otherwise use to specify the duty cycle of the PWM output on OCxA).  If you want flexible control of the TOP, you can only use that timer's OCxB PWM output.

If you want to use both PWM outputs from timer 0 or timer 2, your only control over the PWM frequency comes from your choice of prescaler (and timer 2 has more prescaler options than timer 0), so it will probably be difficult to hit some exactly desired frequency.

Note that this is not the case with timer 1, where you can choose a PWM mode that uses the ICR1 register as TOP (instead of OCR1A, leaving OCR1A free for PWM generation).

One solution is to set up compare match and overflow interrupts for whatever timers you want and use them to generate your own software PWM outputs on whatever pins you want.

- Ben


bens

#7
Jul 11, 2008, 07:49 pm Last Edit: Jul 11, 2008, 07:50 pm by bens Reason: 1
Quote

It does Ben, *thankyou*, that pulled it together for me, I was sort of there with the information from your other posts but I'd not quite got my head round the equation until reading this!

Cheers! :)


I'm happy to help.  Good luck with your project!

- Ben

Siftah

Hmm, well I don't seem to be quite there just yet.

Is it important for the Arduino to be tied to the same ground as the fan?

Currently I have ground and +12v wired from my power supply to the first 2 pins on the fan. The control wire on the fan is then wired to the Arduino, I don't seem to be able to get the fan to change speed though :(

The code I've been using is the following:
Code: [Select]

int controlPin = 3;
void setup()
{
 Serial.begin(9600);
 TCCR2A = 0x23;
 TCCR2B = 0x09;  // select clock
 OCR2A = 79;  // aiming for 25kHz
 pinMode(controlPin, OUTPUT);  // enable the PWM output (you now have a PWM signal on digital pin 3)
 OCR2B = 62;  // set the PWM duty cycle
}

void loop()
{
 OCR2B = 62;
 delay(5000);
 OCR2B = 120;
 delay(5000);
}


If nothing else, hopefully the above code will be spotted by someone trying to do the same thing in the future :)

bens

#9
Jul 12, 2008, 12:49 am Last Edit: Jul 12, 2008, 12:50 am by bens Reason: 1
If your TOP value is 79, setting OCR2B = 62 produces a 78% duty cycle while setting OCR2B = 120 sets a 100% duty cycle.  You gain nothing from OCR2B once you make it greater than the TOP value (e.g. OCR2B = 79 is the same functionally as OCR2B = 120).  It's possible you aren't noticing a speed difference because 78% is so close to 100%.  Maybe you should try duty cycles of 25%, 50%, 75%, and 100%?  To acheive a duty cycle of x%, you need to set OCR2B = OCR2A * x / 100.

If you have access to an oscilloscope, look at the output of your control pin to make sure it's generating the PWM you think it is.  If not, you can do an easy test by generating your own software PWM.  For example:

void setup()
{
 DDRD |= 1 << PD3;  // set pin 3 as an output (the same as pinMode(3, OUTPUT))
}

void loop()
{
 // 25 kHz means 40 us per cycle
 PORTD |= 1 << PD3;  // set pin 3 high (much faster than digitalWrite)
 delayMicrosecond(10);
 PORTD &= ~(1 << PD3); // set pin 3 low (much faster than digitalWrite)
 delayMicrosecond(30);
}

This would create a 25% duty cycle PWM with a frequency of 25 kHz.  If you wanted a 50% duty cycle you would delay for 20 us each time.  A 75% duty cycle would come from 30 us for the first delay and 10 us for the second.  With this code you can be confident that you are getting out the correct PWM.  Observe how your motor reacts to this and see if it's consistent with how it reacts to your hardware PWM code.  If it is, there's something up with your motor or your understanding of how it works.  If it's not, there's a problem with the hardware PWM code (e.g. maybe it's outputing a PWM on a different pin, or one of the register settings is slightly wrong).  If the problem is with your hardware PWM code I can test it out on my end using my oscilloscope, but I'd rather you conducted a few more tests on your own first.

- Ben

Siftah

Quote

If you have access to an oscilloscope, look at the output of your control pin to make sure it's generating the PWM you think it is.  If not, you can do an easy test by generating your own software PWM.  For example:

void setup()
{
 DDRD |= 1 << PD3;  // set pin 3 as an output (the same as pinMode(3, OUTPUT))
}

void loop()
{
 // 25 kHz means 40 us per cycle
 PORTD |= 1 << PD3;  // set pin 3 high (much faster than digitalWrite)
 delayMicrosecond(10);
 PORTD &= ~(1 << PD3); // set pin 3 low (much faster than digitalWrite)
 delayMicrosecond(30);
}



I just tried this and I still don't get any change in the fan speed - perhaps my understanding of how the fan is controlled is wrong.

I'm going off the spec for the 4-pin fans which I've found in this PDF: http://www.formfactors.org/developer%5Cspecs%5CREV1_2_Public.pdf

The relevant portions of it seem to be this section:
Quote

2.4 PWM Control Input Signal
The following requirements are measured at the PWM (control) pin of the fan cable connector:
PWM Frequency: Target frequency 25 kHz, acceptable operational range 21 kHz to 28 kHz
Maximum voltage for logic low: VIL = 0.8 V
Absolute maximum current sourced: Imax = 5 mA (short circuit current)
Absolute maximum voltage level: VMax = 5.25 V (open circuit voltage)


Unfortunately I don't have access to an oscilloscope so it's hard to test the actual PWM output, perhaps I'm missing something obvious :\

I've added an LED to the output and can visually see the LED responding to the differing PWM, so there's definitely a change in the PWM, perhaps the fans I have are off-spec or something along those lines...

bens

Quote
Is it important for the Arduino to be tied to the same ground as the fan?

I missed this question of yours before.  It is crucial that your Arduino and fan share the same ground.  The PWM output is a changing voltage relative to the Arduino's ground.  The only way it will mean anything to the fan is if the voltage on that pin is relative to the fan's ground.

Voltage is always a relative quantity.  It is the difference in voltage that matters to a circuit, because this is what creates the electric field that drives the current.  For example, connecting what one circuit thinks of as 5 V to a completely separate circuit won't achieve anything.  There is no such thing as "5 V" in an absolute sense.  There is only such a thing as a 5V difference between two rails (a.k.a. 5 V relative to something else), where you arbitrarily call one point "ground" and for ease of calculation define that point as "0 V".

- Ben

Siftah

Quote

I missed this question of yours before.  It is crucial that your Arduino and fan share the same ground.  The PWM output is a changing voltage relative to the Arduino's ground.  The only way it will mean anything to the fan is if the voltage on that pin is relative to the fan's ground.
- Ben


AHA! :)

This makes perfect sense now it's pointed out, thankyou (again!) :)

It works like a charm now *happy*

Basically my project is just to build a very simple fan controller to help keep my server 'cupboard' cool. I have a couple of machines which live in a cupboard under my stairs, I've added some venting to the cupboard to draw in cold air from either under the house or outside - depending on where the air temperature is cooler. The Arduino will be a little prototype controller to see how well this method of cooling works.

hmd

thanks Ben

still in the dark i've been trying to understand
TCCRxA/B : to select prescale of the timer & mode
OCxA/B: to select output related to the pin (described earlier)
OCRxA/B : to select the top or bottom overflow (pwm)

the example you gave earlier dont use OCxA/B
timer 2 A/B are not related or i dont get it at all

[edit]void setup()
{
 // configure hardware timer2 to generate a fast PWM on OC2B (Arduino digital pin 3)
 // set pin high on overflow, clear on compare match with OCR2B
 TCCR2A = 0x23;
 TCCR2B = 0x0C;  // select timer2 clock as 16 MHz I/O clock / 64 = 250 kHz
 OCR2A = 249;  // top/overflow value is 249 => produces a 1000 Hz PWM
 pinMode(3, OUTPUT);  // enable the PWM output (you now have a PWM signal on digital pin 3)
 OCR2B = 125;  // set the PWM to 50% duty cycle
}[/edit]

i tried to re use this code for other pins and changing the timer and output compare (no one works)

[edit]One solution is to set up compare match and overflow interrupts for whatever timers you want and use them to generate your own software PWM outputs on whatever pins you want.

- Ben [/edit]

please can  u give one example of code on one pin(ex. pin5) but re-usable for all other pins at around 20khz

thanks again for your quick answer.

hmd

bens

Quote
still in the dark i've been trying to understand
TCCRxA/B : to select prescale of the timer & mode
OCxA/B: to select output related to the pin (described earlier)
OCRxA/B : to select the top or bottom overflow (pwm)

OCxA/B are just the names of the pins that will output timer 2 PMWs A and B.  OC2A is pin 11 on the Arduino (pin PB3 on the mega168) and OC2B is pin 3 on the Arduino (pin PD3 on the mega168).  Using this terminology lets Atmel keep use the same documentation for multiple microcontrollers, even if they use different pins for the PWM outputs.

OCRxA and OCRxB can serve two functions:

1) If TOP is specified as a fixed value (e.g. 0xFF) by the mode, OCRxA controls the duty cycle of the PWM output on pin OCxA and OCRxB controls the duty cycle of the PWM output on pin OCxB.  Specifically, when the timer has a compare match with OCRxA or B, the associated PWM signal will go from high to low or vice versa, depending on whether the PWM is normal or inverted.

2) If TOP is specified as a controllable value by the mode, OCRxA controls the TOP and OCRxB controls the duty cycle.  Put another way, the timer experience a compare match when it equals OCRxB and will overflow when it equals OCRxA.  In this mode, the only usable PWM is generated on pin OCxB (you can't use the PWM on OCxA for much because OCRxA is being used to control the TOP of OCxB).

Note that the above applies mainly to timers 0 and 2.  Timer 1 is special in that you can run it in a mode where ICR1 can be used to control the TOP value of both PWMs A and B.

the example you gave earlier dont use OCxA/B
timer 2 A/B are not related or i dont get it at all

Quote
please can  u give one example of code on one pin(ex. pin5) but re-usable for all other pins at around 20khz

You can use the TCCR2x control registers to disconnect the PWM outputs so that nothing happens on OC2A or OC2B as this code runs.  See the datasheet for more details.

ISR(TIMER2_COMPB_vect)  // interrupt that occurs on timer 2 compare match with OCR2B
{
 PORTD ^= 1 << PD5;  // toggle the output on Arduino pin 5 (much faster than digitalWrite(5, LOW))
}

ISR(TIMER2_OVF_vect) // interrupt that occurs when timer 2 overflows (i.e. when TCNT2 = OCR2A)
{
 PORTD ^= 1 << PD5;  // toggle the output on Arduino pin 5 (much faster than digitalWrite(5, HIGH))
}

For the above code to work, you will need to enable timer 2 compare match A and overflow interrupts.  Once again, the timer 2 section of the datasheet (see the "registers" sub-section) should show you how this is done.

Note that this is not an ideal solution.  If you only need one such PWM output, you really should try to use an actual PWM hardware output so that you don't needlessly waste processing time.  Additionally, this method will result in a slightly distorted PWM if there are delays in executing the ISRs that are driving the PWM.  The longer the delays relative to the timescale of the PWM, the greater the distortion.

- Ben

Go Up