Energy Meter for Wind turbine

My goal was to make an energy meter which recorded watts produced at different wind speeds by my wind turbine. I was trying to space out my readings every 120 loops so that I got a sampling without too much data.

Results are inconsistent ( sometimes 15 mph wind gives 126 watts and sometimes 497 watts) which makes me think there is a delay in recording one of the values either watts or wind speed.

11 mph =556.00 watts,
11 mph =484.00 watts,
13 mph =255.00 watts,
15 mph =126.00 watts,
20 mph =377.00 watts,
15 mph =497.00 watts,
13 mph =409.00 watts,
20 mph =501.00 watts,
11 mph =358.00 watts,

Any feedback would be appreciated.

#include <LiquidCrystal.h>
#include <SD.h>
#include <SPI.h>
#include <SoftwareSerial.h> 
#include <PZEM004T.h>

LiquidCrystal lcd(8,7,3,4,5,6);
PZEM004T pzem(10,11);  // TX,RX
IPAddress ip(192,168,1,1);


File myFile;
int pinCS = 53;
unsigned int mph;
int num = 0;
int mphMaximum = 0;
const int hallSensorPin = 2;                      // connect the hall effect sensor on pin 2
const unsigned long sampleTime = 2500;            //P=rpm * 9 (2.5/T)

void setup() 
{

  pinMode(hallSensorPin,INPUT);
  pinMode(pinCS, OUTPUT);
  Serial.begin(9600);
  pzem.setAddress(ip);
 
   lcd.begin(16, 4);
  lcd.print("Initializing");
  delay(1000);
  lcd.clear();

 
   if (SD.begin())
  {
    Serial.println("SD card is ready to use.");
  } else
  {
    Serial.println("SD card initialization failed");
    return;
  }
}
         
void loop() 
{
 
  int count = 0;
  boolean countFlag = LOW;
  unsigned long currentTime = 0;
  unsigned long startTime = millis();
  while (currentTime <= sampleTime)
  {
    if (digitalRead(hallSensorPin) == HIGH){
      countFlag = HIGH;
    }
    if (digitalRead(hallSensorPin) == LOW && countFlag == HIGH){
      count++;
      countFlag=LOW;
    }
    currentTime = millis() - startTime;
  }
  int mph = 1.3 * count;
 Serial.print(mph);
 Serial.print(" MPH");
 Serial.println();
   if (mph > mphMaximum)
   {
    mphMaximum = mph;
    }
   // lcd.clear();
 

 
  
  float v = pzem.voltage(ip);
  if (v < 0.0) v = 0.0;
  Serial.print(v);Serial.print("V; ");
 
  float i = pzem.current(ip);
  if(i >= 0.0){ Serial.print(i);Serial.print("A; "); }
  
  float p = pzem.power(ip);
  if(p >= 0){ Serial.print(p);Serial.print("W; "); }
  
  float e = pzem.energy(ip);
  if(e >= 0.0){ Serial.print(e);Serial.print("Wh; "); }

  num = num + 1;
  Serial.print(num);
  Serial.println(); 

   if (num > 120)
   {
     if (p > 100)
      {   
         myFile = SD.open("test.txt", FILE_WRITE);
        if (myFile) {    
          myFile.print(mph);
          myFile.print(" mph =");    
          myFile.print(p);
          myFile.println(" watts, ");
          myFile.close(); // close the file
          }
 
       else {
          Serial.println("error opening test.txt");
          }
       }
       num = 0;      
    }

  lcd.clear();
  lcd.setCursor(0, 0); 
  lcd.print(mph);
  lcd.setCursor(6,0);
  lcd.print("MPH");
  lcd.setCursor(13,0);
  lcd.print(mphMaximum);
  lcd.setCursor(17,0);
  lcd.print("MAX");

  lcd.setCursor(0,1);
  lcd.print(p);  
  lcd.setCursor(6,1); 
  lcd.print("Watts");

  lcd.setCursor(4,2);
  lcd.print(e);
  lcd.setCursor(10,2);
  lcd.print("Watt hours");

  lcd.setCursor(4,3);
  lcd.print(i);
  lcd.setCursor(10,3);
  lcd.print("Amps");
}
#include <PZEM004T.h>

You need to provide a link to that library.

  while (currentTime <= sampleTime)
  {
    if (digitalRead(hallSensorPin) == HIGH){
      countFlag = HIGH;
    }
    if (digitalRead(hallSensorPin) == LOW && countFlag == HIGH){
      count++;
      countFlag=LOW;
    }
    currentTime = millis() - startTime;
  }

So, you only care about the wind speed for the first 2.5 seconds after the Arduino resets. Strange.

Personally, I would expect the hall-effect sensor to trigger an interrupt.

I might be missing something, but I don’t see where the code knows anything about watts. It has some uselessly named variable p that I guess is supposed to contain watts, but that is in no way related to wind speed AS YOU MEASURE IT.

This code is completely confused:

void loop()
{
  int count = 0;
  boolean countFlag = LOW;
  unsigned long currentTime = 0;
  unsigned long startTime = millis();
  while (currentTime <= sampleTime)
  {
    if (digitalRead(hallSensorPin) == HIGH)
      countFlag = HIGH;

    if (digitalRead(hallSensorPin) == LOW && countFlag == HIGH)
    {
      count++;
      countFlag=LOW;
    }
    currentTime = millis() - startTime;
  }

startTime must be declared global or static, it must persist outside of loop()

You don’t understand how to detect change, just record the last value read (countFlag isn’t a good name
here, call it ‘previousHallValue’ or something meaningful). That variable must be global or static for the same reasons
as above.

You have a while loop inside loop()… no, that’s defeating the whole point of an event loop.

loop() should be a set of tests for conditions that require handling, and anything complicated should
be in its own, well-named, function, so that loop() doesn’t grow into a confusing clutter.

Why use a glacially slow baud rate - use 115200, not 9600, if you want good responsiveness.

I am sorry for the confusing code. I am a novice. My intention was to read wind speed with the first portion of the code, then read watts in the second portion and finally write to sd card every 120 count when watts are over 100 in the 3rd section.

Paul- link to the library: GitHub - olehs/PZEM004T: Arduino communication library for Peacefair PZEM-004T Energy monitor

thank you for the constructive feed back.

A good guideline is keep most functions to 10 lines or less. Always choose good names for variables and functions.

The energy produced by a wind turbine can vary significantly over very short intervals - parts of a revolution - so you need to sample the voltage and current very frequently. And if you want to compare energy to wind speed then you need to read speed, volts and amps as near as possible at the same time.

Can your wind speed sensor respond as quickly as the energy might change?

...R

Robin2 - that’s a great point. I wasn’t sure if it was my code or the home built anemometer. I intend to get an anemometer which uses analog voltage instead of the hall effect sensor.

I have altered the code as such. I can’t try it until the anemometer arrives but here is what I have. I will probably remove the 120 count sampling for testing to check for inconsistency. Any thoughts?

#include <LiquidCrystal.h>
#include <SD.h>
#include <SPI.h>
#include <SoftwareSerial.h> 
#include <PZEM004T.h>

LiquidCrystal lcd(8,7,3,4,5,6);
PZEM004T pzem(10,11);  // TX,RX
IPAddress ip(192,168,1,1);


File myFile;
int pinCS = 53;
int num = 0;
int mphMaximum = 0;
//const unsigned long sampleTime = 2500;            //P=rpm * 9 (2.5/T)
int wspeed;
int maxspeed = 0;
void setup() 
{

   pinMode(pinCS, OUTPUT);
  Serial.begin(9600);
  pzem.setAddress(ip);
 
   lcd.begin(16, 4);
  lcd.print("Initializing");
  delay(1000);
  lcd.clear();

 
   if (SD.begin())
  {
    Serial.println("SD card is ready to use.");
  } else
  {
    Serial.println("SD card initialization failed");
    return;
  }
}
         
void loop() 
{
 
 
 int sensorValue = analogRead(A0);
 float voltage = sensorValue * (5.0 / 1023.0);
 wspeed =(voltage - .4) * 59; // formula says 36.23837 and not 59
 Serial.println(wspeed);
 if (wspeed > maxspeed) maxspeed = wspeed;
 
  float v = pzem.voltage(ip);
  if (v < 0.0) v = 0.0;
  Serial.print(v);Serial.print("V; ");
 
  float i = pzem.current(ip);
  if(i >= 0.0){ Serial.print(i);Serial.print("A; "); }
  
  float p = pzem.power(ip);
  if(p >= 0){ Serial.print(p);Serial.print("W; "); }
  
  float e = pzem.energy(ip);
  if(e >= 0.0){ Serial.print(e);Serial.print("Wh; "); }

  num = num + 1;
  Serial.print(num);
  Serial.println(); 

   if (num > 120)
   {
     if (p > 100)
      {   
         myFile = SD.open("test.txt", FILE_WRITE);
        if (myFile) {    
          myFile.print(wspeed);
          myFile.print(" mph =");    
          myFile.print(p);
          myFile.println(" watts, ");
          myFile.close(); // close the file
          }
 
       else {
          Serial.println("error opening test.txt");
          }
       }
       num = 0;      
    }

  lcd.clear();
  lcd.setCursor(0, 0); 
  lcd.print(wspeed);
  lcd.setCursor(6,0);
  lcd.print("MPH");
  lcd.setCursor(13,0);
  lcd.print(maxspeed);
  lcd.setCursor(17,0);
  lcd.print("MAX");

  lcd.setCursor(0,1);
  lcd.print(p);  
  lcd.setCursor(6,1); 
  lcd.print("Watts");

  lcd.setCursor(4,2);
  lcd.print(e);
  lcd.setCursor(10,2);
  lcd.print("Watt hours");

  lcd.setCursor(4,3);
  lcd.print(i);
  lcd.setCursor(10,3);
  lcd.print("Amps");
}

An analog anemometer won't be very accurate at low wind speeds.

Paul

Paul.
What do you consider low wind speeds? My target is 15 - 30 mph.

Do you recommend something different?

dillingerkt:
Paul.
What do you consider low wind speeds? My target is 15 - 30 mph.

Do you recommend something different?

My old analog anemometer read about 5 mph low on winds between 0 and 10mph. My digital unit measures exactly between 0 and at least 50mph, which is the fastest we have seen here.

So, low for us is < 10 mph. Your low is a very windy day for us. But there are very few times with 0 wind.

Paul

Do you read the digital anemometer with your Arduino? If so, would you share the code?

dillingerkt:
Do you read the digital anemometer with your Arduino? If so, would you share the code?

I do and would be willing to share. Have in the past. Be aware the actual anemometer head is commercially made and closes a reed relay twice per revolution. The formula to convert # interrupts per second to MPH is based on their commercial version and probably will not apply to your device.

Paul

I understand. I was just considering other options including buying a digital anemometer like yours and incorporating that code with the volt reader I have if this setup doesn't work for me.

My code just counts interrupts for 1 second and then computes the MPH and if greater than previous max, replaces old max. And then moves the MPH to the next spot in a list of 15 MPHs. Finally, computes the average of the 15 entries and displays the max and average on a 2 line LCD. Plenty of time for other stuff to happen.

Paul

Paul, You were correct in the analog anemometer is not as accurate as I would like. Can you please tell me which commercial model you use and show me a sample of the code?

I got mine at a ham radio swap meet for a very low price! Made in Grants Pass, Oregon. The mfg is in the code comments.

My code:

/*Anemometer program using a Met One anemometer. A reed relay is closed twice per revolution.
  Wind speed, in MPH, is computed based on a formula from the company web site.
  MPH is stored in a array of 15 which is averaged over 15 seconds to give average MPH.
  Results are displayed at 1 second intervals.
  Paul Drahn March 2016

  1/5/2019 Modified becaue lcdprint no longer supports float.
*/

volatile unsigned int icounter = 0;  // interrupt count from anemometer reed switch closing/opening
unsigned long currentMillis = 0;  // milliseconds since beginning.
unsigned long previousRPMmillis = 0;  // milliseconds from previous elapsed time test.
unsigned long interval = 1000;  // compute and display wind MPH each second.

float MilesperHour = 0.0;  // wind speed
float HighestMPH = 0.0;  // Highest wind speed found since prog start.
float AverageMPH = 0.0;  // average for last 15 seconds
float peakMPH[15]; // array used to compute 15 second average MPH
byte  i = 0;  // index for average calc
byte  next = 0;  // index for storing next peak MPH

// unsigned long revs = 0;  // difference between current interrupt count and previous count.
//  unsigned long previousCounter = 0;  // interrupt count at previous 1 second process
unsigned int revCnt = 0;  // copy of interrupt counter
static char outstr[7]; // buffer for conversion of float to string 

#include <Wire.h>
#include <LiquidCrystal_I2C.h>

/*-----( Declare Constants )-----*/
/*-----( Deefine objects )-----*/
   
// Set the pins on the I2C chip used for LCD connections:
//                    addr, en,rw,rs,d4,d5,d6,d7,bl,blpol
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);  // Set the LCD I2C address
#define LCDROWSIZE        2
#define LCDCOLSIZE       16


void setup()
{
  lcd.begin(LCDCOLSIZE, LCDROWSIZE);   // initialize the lcd for 16 chars 2 lines, turn on backlight

  lcd.setCursor(0, 0);                  // Splash screen
  lcd.print("AVERAGE:");
  lcd.setCursor(0, 1);
  lcd.print("MAXIMUM:");

  pinMode(2, INPUT_PULLUP);  // pin is LOW when anemometer switch is closed
  attachInterrupt(0, anemoCount, FALLING);  // interrupt when anemometer magnet closes switch. Twice per rev.
}

void loop()
{
  // when 1 second has elapsed, compute and display wind speed.
  currentMillis = millis();  // milliseconds since prog start.

  if ((currentMillis - previousRPMmillis) >= interval)  // compute and print results once per second
  {
    // make a working copy of the counter while disabling interrupts
    cli();
    revCnt = icounter; // Two interrupts per revolution. 
    icounter = 0;   // and reset
    sei();

    previousRPMmillis = currentMillis;  // save current value of milliseconds
   
    computeMPH();  // compute wind speed in miles per hour
    displayMPH();
  }
}

// interrupt code is executed when anemometer magnet closes reed relay.
void anemoCount()
{
  ++ icounter; // count reed relay closing'
}


void computeMPH()    // computation function....
{
  MilesperHour = (revCnt / 0.5589) + 1.0;  // formula based on 014 anemometer calibration web page
  if (MilesperHour == 1.0)  // if anemometer is not rotating, show zero wind speed
  {
    MilesperHour = 0.0;
  }

  if (MilesperHour > HighestMPH) // Save speed of greatest gust of wind.
  {
    HighestMPH = MilesperHour;
  }

  peakMPH[next] = MilesperHour;  // save current MPH in averaging array.
  if (++next == 15)
  {
    next = 0;  // Start over at beginning of averaging array
  }

  AverageMPH = 0.0;  // Compute average wind speed.
  for (i = 0; i < 15; i++) // add up all 1 second readings
  {
    AverageMPH += (peakMPH[i]);
  }
  AverageMPH /= 15.0;   // divide to get average wind speed over 15 seconds.
}


void displayMPH()
{
  lcd.setCursor(9, 0); // blank out any display residue.
  lcd.print("      ");
  lcd.setCursor(9, 1);
  lcd.print("      ");
  
  lcd.setCursor(9, 0);  // now, print new stuff.
  dtostrf(AverageMPH,5, 2, outstr);   // change to allow float to display
  lcd.print(outstr);
  lcd.setCursor(9, 1);

  dtostrf(HighestMPH,5, 2, outstr);  // change to allow float to display
  lcd.print(outstr);
  }

My wife pushes the button to reset the Arduino nano each morning after recording the highest wind speed, so there is no roll-over problem.

Paul

Thank you so much! I am wondering about the " POSTIIVE" in the LiquidCrystal_I2C line. When I verify the code it errors on this as not being declared in this scope.

I cut and pasted the error message below for specific information:

Arduino: 1.8.5 (Windows 7), Board: "Arduino/Genuino Mega or Mega 2560, ATmega2560 (Mega 2560)"

anemometer_02052019:37: error: 'POSITIVE' was not declared in this scope

LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); // Set the LCD I2C address

^

C:\Users\Kyle\Documents\Arduino\anemometer_02052019\anemometer_02052019.ino: In function 'void setup()':

anemometer_02052019:44: error: no matching function for call to 'LiquidCrystal_I2C::begin(int, int)'

lcd.begin(LCDCOLSIZE, LCDROWSIZE); // initialize the lcd for 16 chars 2 lines, turn on backlight

^

C:\Users\Kyle\Documents\Arduino\anemometer_02052019\anemometer_02052019.ino:44:35: note: candidate is:

In file included from C:\Users\Kyle\Documents\Arduino\anemometer_02052019\anemometer_02052019.ino:30:0:

C:\Users\Kyle\Documents\Arduino\libraries\Arduino-LiquidCrystal-I2C-library-master/LiquidCrystal_I2C.h:76:7: note: void LiquidCrystal_I2C::begin()

void begin();

^

C:\Users\Kyle\Documents\Arduino\libraries\Arduino-LiquidCrystal-I2C-library-master/LiquidCrystal_I2C.h:76:7: note: candidate expects 0 arguments, 2 provided

exit status 1
'POSITIVE' was not declared in this scope

This report would have more information with
"Show verbose output during compilation"
option enabled in File -> Preferences.

Works for me! Perhaps this will help...

Paul

Thanks! That makes sense. I will check my library.