Help needed for a computer fan PWM control project

Hi,

I'm working on a computer fan control project that contains three parts with one wire setting using an Arduino Mega2560 :

  1. to set 10 fans at the same speed with 10 PWM outputs on the board.
  2. use PWM control to be able to set 10 different fans at different speed (with different % duty cycle) and each pin outputs to each fan.
  3. set 10 fans at the same speed but the speed can change over time as settled (for example, ramp down first and stay at low speed for 10 seconds and then speed up again).

I'm totally new to Arduino and wasn't great at coding either :disappointed_relieved: so I really need some guidance on the project and this is what I have done so far.

I chose PWM outputs 2..11 on the Mega2560 board as the 10 outputs (because I want to avoid rewiring every time I change different modes I described above, I chose to use one PWM output for each fan). The code I wrote before for part 1) looks like this: (you can type the % duty cycle in the setup section and upload it to the board)

unsigned int dc=20 ; // % duty cycle that you can set yourself
float t; // time

void setup(){
pinMode(2, OUTPUT); //fan1
pinMode(3, OUTPUT); //fan2
pinMode(4, OUTPUT); //fan3
pinMode(5, OUTPUT); //fan4
pinMode(6, OUTPUT); //fan5
pinMode(7, OUTPUT); //fan6
pinMode(8, OUTPUT); //fan7
pinMode(9, OUTPUT); //fan8
pinMode(10, OUTPUT); //fan9
pinMode(11, OUTPUT); //fan10
}

void loop(){
t=40.00*(float)dc/100.00; //unit is "us", gives 25kHz PWM output
digitalWrite(2, HIGH);
digitalWrite(3, HIGH);
digitalWrite(4, HIGH);
digitalWrite(5, HIGH);
digitalWrite(6, HIGH);
digitalWrite(7, HIGH);
digitalWrite(8, HIGH);
digitalWrite(9, HIGH);
digitalWrite(10, HIGH);
digitalWrite(11, HIGH);
delayMicroseconds(t);
digitalWrite(2, LOW);
digitalWrite(3, LOW);
digitalWrite(4, LOW);
digitalWrite(5, LOW);
digitalWrite(6, LOW);
digitalWrite(7, LOW);
digitalWrite(8, LOW);
digitalWrite(9, LOW);
digitalWrite(10, LOW);
digitalWrite(11, LOW);
delayMicroseconds(40.00-t);
}

The fans requires a PWM frequency at 25kHz and that's why I chose 40 us as one period. However, I heard that digitalWrite takes a few us to run (maybe even longer?) and therefore the PWM frequency to the fans are no longer 25kHz, but lower than that. Also, part 2) & 3) are time sensitive (especially 3) because 10 fans should be the same speed at any given time) so I thought I should probably look for some other ways to do this.

I read through some other posts and people suggested use port manipulation (Arduino Reference - Arduino Reference) because that runs much faster and you can turn on/off a few pins at exactly the same time. The reference and playground doesn't give information on Mega2560 because I think it uses ATmega2560 rather than ATmega168 or ATmega8 so the pins/ports are different.

I saw this link from another post shows the ports map for 2560(?)Arduino Pins - Google Drive and therefore wrote this following code with ports correspond to the spreadsheet and first tried to blink the LED on PWM channel 13 but nothing happened (still trying for part 1)):

void setup(){
DDRE = B00111000;//PWM pins 5,3,2 set as output according to the map because pin 5,3,2 are listed as PE5,PE4,PE3. similar with the rest
DDRH = B01111000;//PWM pins 10,8,7,6 set as output
DDRB = B11110000;//PWM pins 4,12,11,9 set as output
DDRG = B00100000;//PWM pin 13 set as output
PORTE &= B11000111;//Turn off pins 5,3,2
PORTH &= B10000111;//Turn off pins 10,8,7,6
PORTB &= B00001111;//Turn off pins 4,12,11,9
PORTG &= B11011111;//Turn off pin 13
}

void loop(){
//trying to blink
PORTG = B00100000;//pin 13 on
delay(1000);//for 1 second
PORTG = B00000000;//pin 13 off
delay(1000);//for 1 second
}

So, I'm wondering if you guys would like to help me out on where I did wrong for the code and any suggestions on how to achieve part 2) & 3). [For part 2), I was thinking of having 10 different % duty cycle input that I can set different numbers in and upload to the board so each fan would run at that assigned % duty cycle until I interrupt or upload new code. And for part 3), I want the fans to run a set-up duty cycle profile over time then either loop back to repeat or just stay at low until I interrupt or upload new code.]

Any help would be really appreciated!

Thanks!
Ash

The line
t=40.00*(float)dc/100.00; //unit is "us", gives 25kHz PWM output

is calculated every loop again, you can do that better in setup. And I think you just can keep the math in integer (long) to speed it up, no need for floats here.

And yes use of PORT commands is the way to go for this

To get the timing right you should measure 10000 iterations, and tweak the timing or make code adaptive

tweaked your code, but as I have no MEGA I could not test it, please give it a try...

//
// 25Khz signal 
//

unsigned int dc = 20;  // % duty cycle that you can set yourself
long t = 0;    

long period = 40;
     
void setup()
{
  Serial.begin(115200);
  Serial.println("start");

  for (int i=2; i< 13; i++) pinMode(i, OUTPUT);

  t = (40 * dc) /100;    //unit is "us", gives 25kHz PWM output
}

void loop()
{
  PORTE |= B00111000;  //Turn on pins 5,3,2
  PORTH |= B01111000;  //Turn on pins 10,8,7,6
  PORTB |= B11110000;  //Turn on pins 4,12,11,9
  delayMicroseconds(t);
  PORTE &= B11000111;  //Turn off pins 5,3,2
  PORTH &= B10000111;  //Turn off pins 10,8,7,6
  PORTB &= B00001111;  //Turn off pins 4,12,11,9
  delayMicroseconds(period - t);
}

Thanks robtillaart, it worked!

Have a question on your setup though. What does "Serial.begin" and "Serial.println" do? I didn't have these lines before but arduino still seems to understand my code and output the correct percentage duty cycle. Is there serial data transmission in this case (the reference says "begin" is for so Serial.begin() - Arduino Reference)? The reference also suggested Mega should use

Serial.begin(speed)
Serial1.begin(speed)
Serial2.begin(speed)
Serial3.begin(speed)

sounds like Mega can transfer data to computer at 4 different rate because it has 4 of something to do it?

Serial.println sounds like it will print something in a GUI? In this case it will print "start" somewhere, but when I compile it, I didn't see anywhere printing "start" (I'm using -0022).

Your code definitely looks cleaner than the one I posted, however, the binary sketch size is large (after I added serial1,2,3 to the code). Does binary size has anything to do with speed or anything? (I'm such a noob..)

Thanks again for the help! And does anyone have suggestions to part 2) and 3)?

The reference also suggested Mega should use

Serial.begin(speed)
Serial1.begin(speed)
Serial2.begin(speed)
Serial3.begin(speed)

sounds like Mega can transfer data to computer at 4 different rate because it has 4 of something to do it?

The Mega has 4 different UARTs, so, yes if can transfer data to 4 different things at 4 different speeds. Only one of those UARTs is physically connected to the Serial <-> USB converter on-board, so the PC can only be connected to Serial.

Serial.println sounds like it will print something in a GUI?

Yes. Open the Serial Monitor to see the output. There's a button on the IDE to do that.

after I added serial1,2,3 to the code

If you don't need them (i.e. have nothing connected to RX1/TX1, RX2/TX2, or RX3/TX3), don't add them.

Does binary size has anything to do with speed or anything

No and yes. Speed is not necessarily affected by code size. That is, it takes the same number of clock cycles to perform a given action, regardless of how much code there is. The time it takes to get back to executing a given instruction again can vary, but that is not a function of code size itself.

And does anyone have suggestions to part 2) and 3)?

Your parts 2 and 3 are just variations on a theme. Once the theme works, the variations should be simple.

One thing to be aware of is that there are physical differences in the individual fans. A given PWM setting (using the built in analogWrite function or bit-banged as in your code) will not make all fans run exactly the same speed. If that is important, you will need to add some means of feedback to each fan, and read that on the Arduino.

Of course, that will interfere with your ability to bit-bang PWM.

You try something, and post back here if it doesn't work.

I have no Mega to play with, but you may find it worthwhile to play with the timers a bit. On an attiny85 I've managed to convince it to cough up PWM output (via analogWrite!) at anything from 1Hz through to 1MHz. 25kHz and 4kHz were a couple of the targets (it was for a PWM fan controller, most want something between 20kHz and 30kHz, some Nidec fans prefer 500Hz to 5kHz, other Nidec fans demand that range!). One nice thing is that the vast majority of PWM computer fans out there will accept a PWM speed far lower than 25kHz, so if you just need it to work, you can use a lower speed.

I believe there's even a library for changing the PWM speed now, though how well it works with the Mega I don't know.

Thanks for the replies!

@Bobnova, yeah, the fan does have a fairly wide PWM frequency range and it's just recommended to be 25kHz. So it's not that crucial to have the frequency set to be exactly 25k, I just think it'll be nice to set it to the recommended value and to have it work. If it takes other PWM frequency to have my project working, I will be fine with it, too.

@PaulS, thanks for the detailed explanation, those are very helpful! I recognize individual fans are different and did some measurements that indicates the speed fluctuation is within 2% which is acceptable for my project so at least I don't have to mess around with bit-bang PWM for now I guess?

However, I did encounter a couple of problems.

I modified the code from robtillaart a little bit and it works all great except when I change the duty cycle to be 100% (dc=100), rather than going up to highest speed, it ramped down to a very low speed. I searched for the reason and found from this post http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1191976081 that delayMicroseconds(0) would delay for 1023 us rather than execute the next line instantly. And delay() doesn't seem to have this problem. Also I want to make sure I'm correct about this: delayMicroseconds won't take fraction numbers like 0.5 or 1.2 (although from the linked post it says delayMicroseconds has a resolution of 0.25 us but I tried those numbers and didn't receive expected result) but delay() will and delay(0) would go onto the next line instantly?

Currently I use another code to turn everything high instead of change to 100% duty cycle in this code (attached). Wondering is there a way to implement 100% into this code as well or I have to use separate code just for 100% case? (self-correct from what I said earlier about the pin correspondence with ports. The correspondence in this post is correct)

unsigned int dc = 80; // % duty cycle that you can set yourself.

unsigned int t = 0;
unsigned int period = 40; // Unit is "us", gives 25kHz PWM output.

void setup() {
for (int i=2; i< 14; i++) pinMode(i, OUTPUT);
t = (40 * dc) / 100; // This returns an integer between 0 and 40.
}

void loop()
{
PORTE |= B00111000; // Turn on pins 3,2,5 (fan 2,1,4)
PORTH |= B01111000; // Turn on pins 9,8,7,6 (fan 8,7,6,5)
PORTB |= B11110000; // Turn on pins 13,12,11,10 (fan 10,9)
PORTG |= B00100000; // Turn on pin 4 (fan 3)
delayMicroseconds(t);
PORTE &= B11000111; // Turn off pins 3,2,5
PORTH &= B10000111; // Turn off pins 9,8,7,6
PORTB &= B00001111; // Turn off pins 13,12,11,10
PORTG &= B11011111; // Turn off pin 4
delayMicroseconds(40 - t);
}

Also I started thinking about part 2) of my project (to have 10 fans at different set speed). Since the % duty cycle can be different for each fan, this is what I wrote.. It looks stupid and didn't work because the loop takes too long to run so the % duty cycle is no long correct. I tried to increase the period (even tried to change delaymicroseconds to delay) but neither would give me anything close to they duty cycle I wanted.. Because each fan can be with different duty cycles (t1..t10 can be different integers), I don't know if there is an elegant way to put them together rather than running them individually and then loop the whole thing to output the right (more reasonable) speed for the fans. :disappointed_relieved:

purged. found the way to do part 2) by using analogWrite function on PWM pins.

Oh, there is another problem I had is that if I increase the percentage duty cycle say from 0 to 80%, the fan will speed up to a pretty high rpm (in my case, about 4400). After it's stable, then upload the code of 80% again, the rpm of the fan is about 4600 rpm after it's stable. Same thing happens with other percentage duty cycle as long as the jump is high. For other duty cycles, the difference between going up slowly (e.g. from 70% to 80% or upload same code twice) and direct big jump is about 100 to 200 rpm. I'm using delta PFC1212DE-F00 fans so I want to know if it's the problem with the fan, microcontroller, power supply (Antec 650W for 10 fans) or there is an ET somewhere hiding at my place messing around with me.. Anyone had similar issues before?