mattgadient.com

MPPT sucks for wind – making my own wind turbine charge controller with an Arduino

After trying various MPPT and PWM controllers for batteries, inverters, testing, etc., I decided the best use of the wind turbine was going to be as a hot water heater. Initially I’d tried a few elements at various resistances, but the (obvious) issues here bothered me a bit too much:

  • If the resistance is too low, the wind turbine can pump lots of wattage into the element at high winds, but it has trouble starting at low wind speeds.
  • If the resistance is too high, it’s easy to get power in low winds, but windy days become a problem and you need to throw the excess power somewhere as the voltage skyrockets.

The solution: a low resistance element, driven by an Arduino and MOSFETs.

First a picture of the end result:

Arduino Wind Turbine MPPT charge controller

<“it wouldn’t win a beauty pageant?! it’s whats on the inside that counts!” comment goes here>

Okay, so it’s not a looker. And it’s not MPPT (I’ll go into why later). But it varies the load based on the voltage and works swimmingly.

I’ll start with the purpose, move on to the “how”, and end with a couple tidbits of code in case you’re aiming to do something similar.

The Intent and Purpose

As I alluded to earlier, a low load works great at low wind speeds and a high load works great at high wind speeds. After all, when the wind is a slight breeze, you want the wind turbine to spin and generate something. A resistance that looks like a dump load isn’t so good. On the other hand, when winds are so insane that you’d normally need a dump load, hey… at 100% duty cycle that heating element is a dump load!

More specifically I wanted the following:

  1. At tiny wind speeds (up to about 5v unloaded), no load put on the turbine. Let it turn.
  2. At low wind speeds (up to about 12v unloaded), place a light load on the turbine. As you can see, it’s pulling under a watt in the picture (no real wind – I picked a great time to take a picture…). And sure, that’s pretty much nothing. But I’d argue that 0.9w is still slightly better than nothing.
  3. At medium wind speeds, start pulling more. Say around 100w at 12v loaded.
  4. At higher wind speeds, directly connect the element (100% duty cycle).

I also wanted to be able to tweak these values easily with code. Maybe start at 3v instead of 5v. Maybe go full load at 18v instead of 24v. Room to experiment and room to make changes when I swap the 7-blade wind turbine assembly in again and want to pull a little more power at the lower voltages.

So putting together something that could do this was the goal.

The How

Okay, so a few critical components:

  • The Arduino Uno (Mega may be smarter if you want to do something fancy)
  • MOSFET(s) – I used the N-Channel HUF76137P (75A 30V) but find something that’ll handle your current and voltage and is switchable with the Arduino’s meager power output.
  • Resistors – for voltage dividers (in addition to current limiting the MOSFETs and other menial tasks). You want the voltage dividers to take the max possible voltage the turbine will ever output and bring it down to 5v so that the Arduino can handle it.
  • Whatever heating element you’re going to use. Low enough resistance and high enough rating to handle whatever your turbine can throw at it.

That’s really about it. Display (as seen) is nice, If doing something similar, I’ll assume you have a bridge rectifier, wires, thermostat (if using one), etc.

As for wiring, mine kind of resembles this:

Arudino Wind Turbine Wiring Diagram

  • Green are the ground wires.
  • Red are the positive wires.
  • Brown are resistors – the ones going to thermostat are voltage dividers which go to the analog input on the Arduino. The ones going to the MOSFET (splitting to ground) are from the Arduino PWM pins – the resistors are to current limit the MOSFET and to let the MOSFET turn off fast by having a path from the gate to ground.
  • Note that this is for an NPN transisitor (it feeds the ground). If you were to use a PNP transistor (feeding positive to the element) it would be wired differently.
  • Note that the Arduino is powered separately, but you’d need to tie into the ground from the rectifier also.

Consider this diagram an overview. It’s just to give you an idea as to what the overall picture looks like, but you’ll want to design something around whatever you intend to do. For example, if doing something similar, you’d want to calculate your own resistor values for both the divider and to the MOSFET to jive with whatever surge current limiting you want.

The code logic I went with looks like this:

  1. Read voltage (through voltage divider which should give max of 5v) and multiply to get original voltage
  2. Plug voltage into equation to get the desired power output in watts
  3. Calculate the actual output power if direct-connected to the 1-ohm resistor (P=V^2 / R)
  4. (desired watts) / (actual watts if direct connected) = duty cycle to use
  5. Convert duty cycle(0.0-1.0) to the (0-255) range
  6. Apply that duty cycle

I used a bench power supply when iterating through the program to make sure everything worked as expected and output all the values to the Serial Console as I made changes. First made sure voltages were read correctly, then code for the calculated and intended watts, then code for the duty cycle, etc.

Some Sample Code

A couple tidbits that you may find interesting or that may help if you’re (again) doing something similar and are wanting to see how I went about it:

//This one will be hard to read. BASICALLY it reads the voltage (from the voltage divider).
//To "smooth" the voltage, it then stores it in an array and takes the average from that array.
//It then checks to see if the average voltage has changed. If not, it does nothing. If it
//  did change, then it runs LOADTABLEVALUES() which is in the next code block and does the
//  actual work of figuring out power/PWM/etc. After LOADTABLEVALUES() is run it changes the PWM.

 void a0load() {
  unsigned long starttime = micros();
    
  a0current = analogRead(A0);

  a0runningtotal -= a0values[a0valuescounter];
  a0runningtotal += a0current;
  a0values[a0valuescounter] = a0current;
  a0valuescounter++;
  if (a0valuescounter == AZERO_VALUES_ARRAY_SIZE) {
    a0valuescounter = 0;
  }
  
  a0runningavg = a0runningtotal / AZERO_VALUES_ARRAY_SIZE;
  if (a0runningavg > a0prevavg+1.1 || a0runningavg < a0prevavg-1.1 || (a0runningavg < 0.02 && a0prevavg >= 0.02)) {
    a0voltagechanged = true;
    if (a0runningavg < 0.02) { a0runningavg = 0; a0current = 0; } // unsigned long timetaken = micros() - starttime; //Just in case voltage is somehow out of range, make them in-range and int-castable... if (a0runningavg > 1023) a0runningavg = 1023;
    if (a0runningavg < 0) a0runningavg = 0.001;

    //Preps tablevoltage (f), tabletruepower (f), tabledutycyclecalculated (f), tabledutycycleactual (f), tablepwmvaluefordutycycle (int)
    loadtablevalues((int)(a0runningavg));

    if (tablepwmvaluefordutycycle != pwmvalueprev) {
      analogWrite(9, tablepwmvaluefordutycycle);
      pwmvalueprev = tablepwmvaluefordutycycle;
      a0pwmchanged = true;
    }
    
          
//    float thevoltage = (a0runningavg * 7.93 / 1024 * 5);
//    Serial.println("Vnum: " + String(a0runningavg) + " - " + String(tablevoltage) + "v. Took " + String(timetaken) + " micro seconds" + " Watts: " + (tabletruepower) );
    a0prevavg = a0runningavg; 
 //   Serial.println("delta " + String(deltatime) + " prevmicros " + String(prevmicros) + " currmicros " + String(currmicros));
  }

Note that most variables were assigned elsewhere – this *might* be an example worth looking through to understand. But this probably isn’t an example you want to actually copy/paste.

//This section is shorter and easier to follow. It takes the voltage we read previously and
//figures out what PWM to apply.

//Takes 412-432us to complete (under half a millisecond)
void loadtablevalues(int inputvltvalue) {

  //TESTING:
  //unsigned long starttime = micros();

  float tableresistorpower = 0;
  //Some different equations I tried.
  //(0.043)x^3
  //new: 0.043(x^3)-4
  //new again: 0.0545(x^3)-40 very low load, 0 starts at 9.25v, 100% starts at 20v (20v*20a=400w)
  //newer: 0.0502(x^3)-2 , 0 starts at 3.3v, 50% load at just over 10v

  //Converting from ADC number to voltage.
  tablevoltage = 1.0 * inputvltvalue * 7.93 / 1024 * 5;

  //The cubic calculated power.
//  tabletruepower = 0.0502 * pow(tablevoltage,3) - 2;
  tabletruepower = 0.3502 * pow((tablevoltage-5.0),3);
  if (tabletruepower < 0) tabletruepower = 0; //The power demanded if turbine connected directly to resistor (100% duty cycle): P = V^2 * R, used to calculate power at 100% PWM tableresistorpower = pow(tablevoltage,2) / RESISTOR_OHMS; if (tableresistorpower > 0.001) {
    tabledutycyclecalculated = tabletruepower / tableresistorpower;
  } else {
    tabledutycyclecalculated = 0;
  }
  if (tabledutycyclecalculated >= 1) {
    tabledutycyclecalculated = 1;
    tabletruepower = tableresistorpower;
  }

  //Will range from 0-255
  tablepwmvaluefordutycycle = (int)(tabledutycyclecalculated * 255.0);
  if (tablepwmvaluefordutycycle > 255) tablepwmvaluefordutycycle = 255;
  if (tablepwmvaluefordutycycle < 0) tablepwmvaluefordutycycle = 0;

  //Get the actual duty cycle percentage...
  tabledutycycleactual = 1.0 * tablepwmvaluefordutycycle/255;

  
  //TESTING:
//  unsigned long totaltime = micros() - starttime;
//  Serial.println ("Took " + String(totaltime) + " to complete");
//  Serial.println ("For voltage #" + String(inputvltvalue) + " (" + String(tablevoltage) + "v), watts (true/resistor) are " + String(tabletruepower) + "w/" + String(tableresistorpower) + "w, Duty cycle: " + String(tabledutycyclecalculated) + " " + String(tablepwmvaluefordutycycle));
}

Note that tabletruepower = 0.3502 * pow((tablevoltage-5.0),3); is where I plunk in the actual equation.

As a simple example of messing with this, if we wanted 400 watts at 20 volts we might try something like:
DESIRED_WATTS=0.05*(VOLTAGE_READ^3)
…in that case, 1 volt would be 0.05 watts, 2 volts would be 0.4 watts, 12 volts would be 86.4w, 18 volts would be 291.6w, 19 volts would be 342.95w, 20 volts would be 400w, etc.

You can tweak values in the equation to mess with the curve. Using an online cubic equation calculator/graph is a good way to experiment with values if you know you want say X watts at 12 volts and Y watts at 24 volts.

Other Options

  1. It’s possible to simply create a “voltage vs PWM” lookup table if you’d like to simplify things. Take all 255 possible voltage values that can be read from analogRead(), and assign a PWM for each of them. Then when you read the voltage, just lookup the PWM from your table, use it, and you’re golden.
  2. One other option I started was having the Arduino learn perfect values over time and add them to a table. For example:
    1. if voltage has been constant for awhile, assume wind speed is currently constant
    2. increase/decrease PWM to see if there’s a point where power is improved after waiting a few seconds
    3. if so, remember it. Start again and repeat the process.
    4. If you hit that “good” point consistantly, store that value.

    This would be closer to a true MPPT learning algorithm. The big issue I ran into was memory. You want to save lots of values from lots of tests to make sure your results are good and that wind wasn’t changing during those tests. Unfortunately the Uno doesn’t have sufficient memory to really store all that even using a bunch of memory saving tricks. So at best you’d get something kind-of workable and at worst you’d have a big unfortunate mess of bad data.

If you want to experiment with a lot of different equations, there’s always the possibility of hooking up a button and using it to switch between them. You’d undoubtedly want a display or at least an LED indicator though so you can see which equation is currently running.

The End

As much as it took a lot of time to put the whole unit (and code) together, this is one endeavour I’m glad I went through. With all the headaches I’ve hit with off-the-shelf items, I’ve finally got something that works really well and didn’t cost an arm and a leg. It may not be pretty on the outside, but hey…

0 Comments

 | Leave a Comment

    Leave a Comment

    You can use an alias and fake email. However, if you choose to use a real email, "gravatars" are supported. You can check the privacy policy for more details.

    To reduce spam, I manually approve all comments, so don't panic if your comment doesn't show up immediately.