Go Down

Topic: Control PWM fan speed depending on tempature (Read 68970 times) previous topic - next topic

afremont

#45
Mar 28, 2013, 03:01 am Last Edit: Mar 28, 2013, 07:22 am by afremont Reason: 1


EDIT:  Looks like delayMicroseconds also can't generate anything less than 5uS.  What's worse is that it always 5uS off.  The delay it generates is 5uS longer than what you asked for  :(

EDIT2:  After a closer look, it appears that delayMicroseconds is off by 4uS from what you asked for.  Hmm..... that would be one timer tick.


The reference manual says:

Quote

This function works very accurately in the range 3 microseconds and up. We cannot assure that delayMicroseconds will perform precisely for smaller delay-times.


However, also note that micros() has a resolution of 4 microseconds so if you're using that to measure the delay duration you might get misleading answers.


Three things:
1)  All delays appear to be off, not just the short ones.
2)  I used my oscilloscope to measure it.
3)  It looks like a delayMicroseconds(0) results in an approx. 18mS delay.

EDIT:  My inner loop takes approx 48uS to execute.  I'm pretty confident that my scope is not off by 20%.  ;)

EDIT2:  After thinking on it, I believe it to be the overhead of calling the function that is responsible for the added time.  There still exists a bug when calling with 0 for the delay time.
Experience, it's what you get when you were expecting something else.

afremont

#46
Mar 28, 2013, 03:05 am Last Edit: Mar 28, 2013, 03:26 am by afremont Reason: 1
Try moving your call down past the next curly brace.  You are calling the getRPMs function while it's beating the fan at 25kHz, try doing it between duty cycle variations.

EDIT:  This was just a test to see if the high PWM rate would work.  This is not the best way to be generating it; not by a long shot.

EDIT2:  As for slowing it down more, you probably won't be able to get it below 1200RPM.  I couldn't use 0 for the delay call, so that and the extra overhead from calling the delayMicroseconds function made my minimum duty cycle pretty big (between 15 and 20%)
Experience, it's what you get when you were expecting something else.

afremont

#47
Mar 28, 2013, 05:55 am Last Edit: Mar 28, 2013, 09:36 am by afremont Reason: 1
Here is some code that hijacks Timer2 to generate PWM signals on Pin 3 and/or Pin 11 simultaneously with different duty cycles.  You can comment out the parts for the pin that you don't need.  Put this stuff in setup() and then to set the duty cycle just load it into OCR2A(Pin11) or OCR2B(Pin3) depending.  You load a value from 0 to 255 just like with analogWrite()  The pulse rate is kinda high, but lets see if it works before getting more complicated with it.  Each duty cycle increment adds 125nS to the pulse width.  So 0 = 0nS, 1 = 125nS, 2 = 250nS ... 254 = 31.75uS, 255 = 100% on.  This is MUCH more precise than the delayMicroseconds stuff and it doesn't block and runs with no jitter.

Code: [Select]

// generate 31.37kHz PWM pulse rate on Pin 3 and/or Pin 11
 pinMode(11, OUTPUT);  // OCR2A sets duty cycle
 pinMode(3, OUTPUT);   // OCR2B sets duty cycle
// Uncomment one of the three following lines to enable PWM on Pin 3 or Pin 11
//  TCCR2A = 0x81;   // Pin 11
//  TCCR2A = 0x21;   // Pin 3
 TCCR2A = 0xA1;     // Pin 11 and Pin 3 (BOTH)
// Set prescaler  
 TCCR2B = 0x01;   // prescaler = 1
// Initialize duty cycle(s) to zero(0)
 OCR2A = 0;    // duty cycle for Pin 11
 OCR2B = 0;    // duty cycle for Pin 3


Numbers above 77 loaded into the OCR2x register should begin to increase the fan speed above the minimum speed as that represents the 30% duty cycle threshold in the datasheet.  Anything less than that should result in a 1200RPM fan speed.  Maximum speed is 5400RPM according to the manufacturer.  To change duty cycle you just do something like this:
OCR2A = 77;   // 30% duty cycle
or
OCR2A = 255;  // 100% duty cycle

EDIT:  Here are the industry specs for the fans:
http://www.formfactors.org/developer%5Cspecs%5CREV1_2_Public.pdf

It specifies a PWM pulse rate of 21kHz to 28kHz, I'm a little high but I think that can be fixed.  I'm working on it. :)
Experience, it's what you get when you were expecting something else.

PeterH

getRPMS() is a very slow function which means your pwm duty cycle is going way down.

You need to either generate the PWM in hardware, or generate it in an interrupt handler, or change your RPM measuring code to be non-blocking, or change it to be interrupt based.

afremont

#49
Mar 28, 2013, 06:39 pm Last Edit: Mar 28, 2013, 06:58 pm by afremont Reason: 1
Here is a sample sketch that generates a 25kHz PWM signal on Pin 3.  You set the duty cycle by loading OCR2B.  Don't change OCR2A as it sets the pulse rate.  Your duty cycle is set by loading 0 thru 79 into OCR2B.  Even at 0%, the CPU generates a 500nS "glitch" pulse.  At 100% duty cycle, it is glitchless.

Code: [Select]

const int PWMPin = 3;
void setup() {
// generate 25kHz PWM pulse rate on Pin 3
 pinMode(PWMPin, OUTPUT);   // OCR2B sets duty cycle
// Set up Fast PWM on Pin 3
 TCCR2A = 0x23;     // COM2B1, WGM21, WGM20
// Set prescaler  
 TCCR2B = 0x0A;   // WGM21, Prescaler = /8
// Set TOP and initialize duty cycle to zero(0)
 OCR2A = 79;    // TOP DO NOT CHANGE, SETS PWM PULSE RATE
 OCR2B = 0;    // duty cycle for Pin 3 (0-79) generates 1 500nS pulse even when 0 :(
}

void loop() {
 unsigned int x;
 // ramp up fan speed by increasing duty cycle every 200mS, takes 16 seconds
 for(x = 0; x < 80; x++) {
   OCR2B = x;    // set duty cycle
   delay(200);
 }  
}


EDIT:  Oops, fixed comments.  This is pretty much the best way I know to do it.  You load one field to change the duty cycle whenever you want.  No blocking, no glitches from library or application code running with interrupts disabled.  Absolutely zero overhead for the CPU.  Even Timer0 can't muck it up.  ;)  Code improvements welcome.

One big con though, even when set to 0, it generates a 500nS pulse at 25kHz.  That's the way the CPU works and the only to get a true 0% duty cycle is to disable the timer or set the pin to input mode, but that will make the fan run at full speed.
Experience, it's what you get when you were expecting something else.

StealthRT

Seems good now... But it does seem to have the same slow speed as before... the RPM output is:
Quote

0
1
2
3
4
5
2320
6
2184
7
2152
8
2089
9
2047
10
11
1946
12
1955
13
14
1923
15
1878
16
1907
17
18
1895
19
1865
20
21
1876
22
1925
23
1909
24
1969
25
2011
26
2056
27
2102
28
29
2173
30
2172
31
2250
32
2245
33
2285
34
35
2359
36
2392
37
38
2452
39
2532
40
2537
41
2622
42
43
2644
44
2677
45
2708
46
47
48
2819
49
2916
50
51
2977
52
3022
53
3053
54
55
3134
56
3098
57
3192
58
3172
59
3282
60
3251
61
3293
62
3306
63
64
3376
65
3478
66
3459
67
3555
68
3513
69
3631
70
3643
71
3679
72
73
3656
74
75
76
3674
77
3690
78
3724
79
3733
0


The code i am using is this:
Code: [Select]

int pwmPin     = 3; // digital PWM pin 9
int pwmVal     = 1; // The PWM Value
unsigned long time;
unsigned int rpm;
String stringRPM;

void setup()
{
    // generate 25kHz PWM pulse rate on Pin 3
    pinMode(pwmPin, OUTPUT);   // OCR2B sets duty cycle
    // Set up Fast PWM on Pin 3
    TCCR2A = 0x23;     // COM2B1, WGM21, WGM20
    // Set prescaler 
    TCCR2B = 0x0A;   // WGM21, Prescaler = /8
    // Set TOP and initialize duty cycle to zero(0)
    OCR2A = 79;    // TOP DO NOT CHANGE, SETS PWM PULSE RATE
    OCR2B = 0;    // duty cycle for Pin 3 (0-79) generates 1 500nS pulse even when 0 :
 
    digitalWrite(2, HIGH);   // Starts reading
    Serial.begin(9600);
}

void loop()
{
  unsigned int x;
  // ramp up fan speed by increasing duty cycle every 200mS, takes 16 seconds
  for(x = 0; x < 80; x++) {
    OCR2B = x;    // set duty cycle
    getRPMS();
    Serial.println(x);
    delay(200);
  }
}

char getRPMS() {
  time = pulseIn(2, HIGH);
  rpm = (1000000 * 60) / (time * 4);
  stringRPM = String(rpm);
 
  if (stringRPM.length() < 5) {
    Serial.println(rpm, DEC);
  }
}


I appreciate the help, afremont! :)

afremont

Are you sure that the RPM calculation is right?  Also, you are calling the getRPM function immediately after changing the duty cycle.  You need to give it some time to come up to speed.  Move the getRPM call after the delay(200) and maybe increase the length of the delay to give the fan time to ramp up to the new speed.  By the time you get to 79, the output Pin 3 is continuously high so I don't know how it could run any slower than when just connecting the PWM pin straight to +5V as that is what is at the pin.
Experience, it's what you get when you were expecting something else.

afremont

#52
Mar 29, 2013, 12:38 am Last Edit: Mar 29, 2013, 12:39 am by afremont Reason: 1
The specs document that I posted a link for, says that the fan will produce two pulses per revolution on the TACH wire.  It doesn't specify anything about the duty cycle of a pulse.  I think you need to at least measure the full period of one pulse waveform, not just the HIGH portion.  In other words, something like this:
Code: [Select]

 time = pulseIn(2, HIGH);
 time += pulseIn(2, LOW);
 rpm = (1000000 * 60) / (time * 2);
.....
Experience, it's what you get when you were expecting something else.

PeterH

Rather than using pulseIn() to measure the duration of a pulse, I thing you would be better off counting the number of pulses. Given the unknown duty cycle of the pulse, I suggest an interrupt would be the easiest way to do that reliably.

afremont

#54
Mar 29, 2013, 01:28 am Last Edit: Mar 29, 2013, 02:03 am by afremont Reason: 1

Rather than using pulseIn() to measure the duration of a pulse, I thing you would be better off counting the number of pulses. Given the unknown duty cycle of the pulse, I suggest an interrupt would be the easiest way to do that reliably.


I don't think it's really necessary since pulseIn should be able to do it fine as long as it measures both halves of the waveform.  I feel that, at only 20pps at 1200RPM, it would eat up a lot of time to count enough pulses to get an accurate measurement.  I think measuring the period is the way to go.  

Using an interrupt and the code that I've pasted a dozen times, the OP could precisely measure the period but it would mean hijacking Timer1.  There are plenty of other ways to handle it with an ISR that, while being less precise in the timing, wouldn't require hijacking Timer1.

EDIT:  Oops, at 1200RPM it would be 40pps, not 20. 
Experience, it's what you get when you were expecting something else.

PeterH


pulseIn should be able to do it fine as long as it measures both halves of the waveform.


It doesn't do that, though, does it? It only measures the duration of one half of the pulse.

afremont



pulseIn should be able to do it fine as long as it measures both halves of the waveform.


It doesn't do that, though, does it? It only measures the duration of one half of the pulse.


Yes, it only measures one half of the pulse, either the HIGH part or the LOW part.  But if you just call it to do both jobs, it will give you the proper answer, or I would certainly hope so anyway.  I showed an example of summing the two halves a couple of posts back.  Of course, it would be summing the halves of two different pulses, I don't think that is likely to matter much.  I'm going to dig up a four wire fan if I have one and try this all out this weekend and look at it on the scope.

Code: [Select]

  time = pulseIn(2, HIGH);     //  measure HIGH part of the pulse
  time += pulseIn(2, LOW);    //  add in the LOW part of another pulse
  rpm = (1000000 * 60) / (time * 2);    // calculate RPM based upon the fan generating two pulses/rev
.....
Experience, it's what you get when you were expecting something else.

afremont

#57
Mar 29, 2013, 08:09 pm Last Edit: Mar 29, 2013, 08:44 pm by afremont Reason: 1
Following up.  I found an Intel CPU fan that uses four wires.  It is sitting here running between 1000 and 2600RPM as measured by pulseIn and depending upon the duty cycle applied to the PWM (blue) wire.

I had to use a pullup resistor on the TACH (green) wire to +5V to get reliable information from it.  It appears that the TACH output is pretty much a square wave, but the specs don't guarantee that.

Here are the results.  First column is OCR2B number (0-79).  Second column is number of microseconds returned by pulseIn() for the HIGH portion of the TACH signal.  The third column shows the total period of the TACH signal (HIGH + LOW).  The final column shows the calculated RPM.  Note how once the duty cycle falls below about 40%, it starts to increase the RPM a little.  

Code: [Select]

79 6226 12251 2634
78 5821 11468 2615
77 5782 11391 2633
76 5783 11385 2635
75 5783 11394 2632
74 5828 11479 2613
73 5794 11588 2588
72 5992 11799 2542
71 6080 11966 2507
70 6056 12122 2474
69 6147 12304 2438
68 6249 12500 2400
67 6455 12721 2358
66 6587 12974 2312
65 6567 13141 2282
64 6822 13435 2232
63 6928 13663 2195
62 6948 13885 2160
61 7225 14228 2108
60 7370 14521 2065
59 7517 14815 2024
58 7547 15092 1987
57 7879 15520 1932
56 8072 15902 1886
55 8275 16294 1841
54 8327 16666 1800
53 8729 17193 1744
52 8800 17610 1703
51 9052 18117 1655
50 9317 18647 1608
49 9804 19315 1553
48 9948 19918 1506
47 10493 20688 1450
46 10879 21437 1399
45 11308 22282 1346
44 11764 23177 1294
43 12283 24208 1239
42 12589 25199 1190
41 13452 26515 1131
40 13828 27662 1084
39 14828 29233 1026
38 15027 29598 1013
37 15056 29669 1011
36 15059 29658 1011
35 15033 29619 1012
34 15010 29574 1014
33 14991 29529 1015
32 14963 29481 1017
31 14935 29427 1019
30 14900 29354 1022
29 14855 29277 1024
28 14542 29098 1030
27 14781 29128 1029
26 14462 28928 1037
25 14693 28947 1036
24 14379 28765 1042
23 14607 28779 1042
22 14282 28572 1049
21 14512 28589 1049
20 14211 28434 1055
19 14162 28327 1059
18 14105 28224 1062
17 14073 28147 1065
16 14040 28087 1068
15 14006 28016 1070
14 13948 27910 1074
13 13925 27858 1076
12 13892 27796 1079
11 13867 27758 1080
10 13846 27700 1083
9 13817 27650 1084
8 13774 27566 1088
7 14009 27610 1086
6 13749 27507 1090
5 13960 27521 1090
4 13668 27361 1096
3 13910 27412 1094
2 13636 27281 1099
1 13884 27363 1096
0 13763 27545 1089
Experience, it's what you get when you were expecting something else.

smart-aleck

I tried this code on my Arduino UNO R3 and it seems to work nearly perfectly in conjunction with a 4-wire Dell Server fan and a potentiometer.  I was also able to add an ethernet shield and give some basic control of the speed and monitor the RPMs via a webpage.  I'm trying to port this code over to my TinkerKit LCD board though, and I'm running into some trouble.

The TinkerKit LCD is essentially a slimmed-down Leonardo with TinkerKit connectors.  I have access to the D12 connector which is pin 11 and should be the 8-bit Timer0, however after I changed the registers to use Timer0 it didn't seem to have an effect.  I read a timer tutorial and have been able to blink an LED on this pin but cannot wrack my brain enough to figure out the changes I need to make for the PWM fan.

I'd appreciate if one of the fellows who worked with and/or wrote the original code could take a second and see if this is possible, can anyone assist?  I made a new forum post specifically about this issue, but hoping I'd get some replies if I responded to this thread.  My original thread/posting is here:  http://forum.arduino.cc/index.php?topic=190788.0

Thanks in advance!

nag1089

Hello,

I am trying to use your code to control a 4 pin fan. I cannot get my fan to slow to 0. Can you please post the code you used to achieve this. This is the fan that I am using.
http://www.arctic.ac/us_en/arctic-f8-pwm.html

I would really appreciate any help that you can offer.

Go Up