Assigning variables for ISR's on a weather station

I have a modified script to gather readings from an anenometer (reed switch), pluvometer (reed switch), and wind vane (resistor array).

As-is it seems to work fine.

I'm trying to break it out into a separate .cpp and .h file in order to clean up the main loop. Here I've run into problems, and can't figure out how to declare the variables and functions such that the ISR's have access at runtime. However I arrange things I get errors indicating no variables are available, or all variables are defined multiple times.

Any help arranging the headers would be much appreciated, I'm afraid I've tried every possible combination already :confused:

//Main

#include "header.h"

void setup(){
    pinMode(ANEMOMETER_PIN, INPUT_PULLUP); 
    pinMode(VANE_POWERPIN, INPUT);
    pinMode(VANE_PIN, OUTPUT); 
    analogWrite(VANE_POWERPIN, HIGH);
    pinMode(RAIN_PIN, INPUT_PULLUP); 
    attachInterrupt(ANEMOMETER_PIN, anemometerISR, RISING); 
    attachInterrupt(RAIN_PIN, pluvometerISR, RISING);

Header.h

#ifndef HEADER_H
#define HEADER_H
#pragma once

#define ANEMOMETER_PIN 9
#define VANE_PIN A1
#define VANE_POWERPIN A5
#define RAIN_PIN 8  

#define ANEMOMETER_CONV 2.4*0.277778 // m/s
#define PLUVOMETER_CONV 0.2794*60*60 //mm/h

extern void anemometerISR();
extern void pluvometerISR();

#endif

WX.cpp

#include <Arduino.h>
#include "WX.h"
#include "header.h"

//All functions reside here

WX.h

const byte anemometerPin = ANEMOMETER_PIN; 
const byte pluvometerPin = RAIN_PIN; 
const byte windVanePin = VANE_PIN; 
const byte windVaneRefPin = VANE_POWERPIN; 
volatile unsigned long sTime; 
volatile float pulseTime; 
volatile float culPulseTime; 
volatile bool startRev; //tracks when measurement start
volatile unsigned int avgWindCount; 
volatile unsigned int pluvCount; //stores pluvometer counts  
volatile int vaneSample[51];
int vaneSampleSize;
 
unsigned int vaneSampleIdx;
unsigned int windDirBin[16];
unsigned long WX_Timer; //used to track how often to communicate data
float windDir;

Below is the full (working) script in a single file:

/* Modified from :
** @file    ADSWeather.cpp
** @author    John Cape
*/
const byte anemometerPin = 9; 
const byte pluvometerPin = 8; 
const byte windVanePin = A1; 

int vaneSampleSize = 51;

#define ANEMOMETER_CONV 2.4*0.277778 // m/s
#define PLUVOMETER_CONV 0.2794*60*60 //mm/h

volatile unsigned long sTime = 0; //stores startRev time 
volatile float pulseTime = 0; //stores time between one anemomter relay closing and the next
volatile float culPulseTime = 0; //stores cumulative pulsetimes 
volatile bool startRev = true; //tracks when a new anemometer measurement startRevs
volatile unsigned int avgWindCount = 0; //stores anemometer relay counts 
volatile unsigned int pluvCount = 0; //stores anemometer relay counts 
volatile int vaneSample[51];

unsigned int vaneSampleIdx = 0;
unsigned int windDirBin[16];
unsigned long WX_Timer = 0; //used to track how often to communicate data
float windDir;

void setup() {
  pinMode(anemometerPin, INPUT_PULLUP); 
  //  pinMode(windVaneRefPin, OUTPUT); 
  //  analogWrite(windVaneRefPin, HIGH);
  pinMode(pluvometerPin, INPUT_PULLUP); 
  pinMode(windVanePin, INPUT);
  attachInterrupt(anemometerPin, anemometerISR, RISING); 
  attachInterrupt(pluvometerPin, pluvometerISR, RISING); 
  WX_Timer = millis(); //reset loop timer
}

void loop() {

  if ((millis() - WX_Timer) > 5000) { //See if it is time to transmit

    float aWSpeed = getAvgWindSpeed(culPulseTime, avgWindCount); //calculate average wind speed
    culPulseTime = 0; //reset cumulative pulse counter
    avgWindCount = 0; //reset average wind count

    float aPrecip = getAvgPrecip(millis() - WX_Timer, pluvCount); 
    pluvCount = 0; 

    float aFreq = 0; //set to zero initially
    if (pulseTime > 0.0) aFreq = getFreq(pulseTime); 
    float wSpeed = getWind(aFreq); 

    windDir = readWindDir();
    //windDir = analogRead(windVanePin);

    Serial.begin(9600); //serial uses interrupts 
    Serial.print(wSpeed);
    Serial.print(" Current_average_wind_speed ");
    Serial.print(aWSpeed);
    Serial.print(" Average_precipitation ");
    Serial.print(aPrecip);
    Serial.print(" Average_direction ");
    Serial.println(windDir);
    Serial.end(); //serial uses interrupts 

    startRev = true; 
  }
}

//using time between anemometer pulses calculate frequency of anemometer
float getFreq(float pTime) {
  return (1 / pTime);
}
//Use anemometer frequency to calculate wind speed in MPH, note 2.5 comes from anemometer data sheet
float getWind(float freq) {
  return (freq * ANEMOMETER_CONV);
}
float getPrecip(float freq) {
  return (freq * PLUVOMETER_CONV);
}


//Calculates average wind speed over given time period
float getAvgWindSpeed(float cPulse, int per) {
  if (per) return getWind(getFreq((float)(cPulse / per)));
  else return 0; 
}

//Calculates average wind speed over given time period
float getAvgPrecip(float cPulse, int per) {
  if (per) return getPrecip(getFreq((float)(cPulse / per)));
  else return 0; 
}

//This is the interrupt service routine (ISR) for the anemometer input pin
void anemometerISR() {
  unsigned long cTime = millis(); //get current time
  if ((cTime - sTime) > 2500) startRev = true; //if the wind speed has dropped below 1MPH than set it to zero

  if (!startRev) { //This is not the first pulse and we are not at 0 MPH so calculate time between pulses
    // test = cTime - sTime;
    pulseTime = (float)(cTime - sTime) / 1000;
    culPulseTime += pulseTime; //add up pulse time measurements for averaging
    avgWindCount++; //anemomter went around so record for calculating average wind speed
  }
  sTime = cTime; //store current time for next pulse time calculation

  //sample the wind direction
  vaneSample[vaneSampleIdx] = analogRead(windVanePin);
  vaneSampleIdx++;
  if (vaneSampleIdx >= vaneSampleSize)
  {
    vaneSampleIdx = 0;
  }

  startRev = false; 
}

//This is the interrupt service routine (ISR) for the anemometer input pin
void pluvometerISR() {
  pluvCount++; 
}


//Updates the wind direction internal state.
** @file    ADSWeather.cpp
** @author    John Cape
** @copyright Argent Data Systems, Inc. - All rights reserved
*/
float readWindDir()
{
  unsigned int maximum, sum;
  unsigned char i, j, max_i;

  //Clear wind vane averaging bins
  for (i = 0; i < 16; i++)
  {
    windDirBin[i] = 0;
  }

  //Read all samples into bins
  for (i = 0; i < vaneSampleSize; i++)
  {
    setBin(vaneSample[i]);
  }

  //Calculate the weighted average
  //Find the blokc of 5 bins with the highest sum
  maximum = 0;
  for (i = 0; i < 16; i++)
  {
    //get the sum of the next 5 bins
    sum = 0;
    for (j = 0; j < 5; j++)
    {
      sum += windDirBin[(i + j) & 0x0F];
    }
    if (sum > maximum)
    {
      maximum = sum;
      max_i = i;
    }
  }
  sum = 0;
  for (i = 1; i < 5; i++)
  {
    sum += (windDirBin[(max_i + i) & 0x0F] * i);
  }
  sum = ((max_i * 45) + ((sum * 45) / maximum) >> 1) % 360; //Convert into degrees
  return sum;
}

//fwind direction using consensus averaging.
void setBin(unsigned int windVane)
{
  //Read wind directions into bins
  unsigned char bin;
  if ( windVane > 940) bin = 12;    //W
  else if (windVane > 890) bin = 14; //NW
  else if (windVane > 820) bin = 13; //WNW
  else if (windVane > 785) bin = 0; //N
  else if (windVane > 690) bin = 15; //NNW
  else if (windVane > 630) bin = 10; //SW
  else if (windVane > 590) bin = 11; //WSW
  else if (windVane > 455) bin = 2; //NE
  else if (windVane > 400) bin = 1; //NNE
  else if (windVane > 285) bin = 8; //S
  else if (windVane > 240) bin = 9; //SSW
  else if (windVane > 180) bin = 6; //SE
  else if (windVane > 125) bin = 7; //SSE
  else if (windVane > 90)  bin = 4; //E
  else if (windVane > 80)  bin = 3; //ESE
  else bin = 5;
  windDirBin[bin]++;
}

and why must you break it up into .cpp/.h files? If you break it up into other .ino files, all in the same directory, they will be combined into one file during compilation.

If you must go down the .cpp/.h file route, you declare your variables in your .h file

extern int myVar;

and then you define your variables in your .cpp file

#include "header.h"
int myVar;

See Reply #3 Here.

your sketch files compiled for me after i

  • added the closing brace to setup() and added void loop (void) {}
  • added #include "header.h" to wx.h

of course the files you posted do not include all the routines in your complete sketch

making code more modular is typically a good. breaking code up into separate files doesn't necessarily make it modular

the code put in a .cpp file should be independent of the code outside of it. it shouldn't be written for specific pins or other constants, they should be passed to it or can be used to initialize the use of a function using a corresponding initialization function.

the initialization function would replace code invoked in setup().

a header file should be able to be included in more that one .cpp file. that implies that variables are not "defined" in a header file. a header file the only includes other header files defeats the purpose and is not a good practice. your header.h looks correct. wx.cpp should include what is in your wx.h

variables and functions unique to the module should be declared in the .cpp file, possibly as static so that the cannot be accessed from outside the function.

the functions and possibly variables that do need to be accessed outside the .cpp file should be "declared" in the header file and that header file included in other .cpp/.ino files that need access.

static variables and functions in the module's .cpp file are "hidden" from the code outside of it. these "object oriented design" concepts that predated "object oriented programming languages"

Boom! Thanks a lot (I think this is the correct solution, at least it compiles and looks tidy)

RE modularity: This is an element of a project that includes a wide range of sensors, so I have to make it modular before I lose my mind. That said, I've never done this before, so just went with the first option that presented itself (.cpp & .h). Thanks a lot for the perspectives.

I got stuck on this problem as I didn't know where to declare the interrupt requests and their related variables. I wonder if you can expand on the concept of the initilization function... the 'Setup' code bothers me.

Interestingly it would compile with variables defined in both WX.h and Main.ino, without the 'extern' qualifier. Somehow that's clear, they are then local variables, but of course I would get the incorrect answer.

Main.ino

#include "header.h"

void setup(){
    pinMode(ANEMOMETER_PIN, INPUT_PULLUP); //set interrupt pin to input pullup
    pinMode(VANE_PIN, OUTPUT); //set the reference pullup pin voltage
    //pinMode(VANE_POWERPIN, INPUT); 
    //analogWrite(VANE_POWERPIN, HIGH);
    pinMode(RAIN_PIN, INPUT_PULLUP); //set interrupt pin to input pullup
    attachInterrupt(ANEMOMETER_PIN, anemometerISR, RISING); //setup interrupt on anemometer input pin, interrupt will occur whenever falling edge is detected
    attachInterrupt(RAIN_PIN, pluvometerISR, RISING); //setup interrupt on anemometer input pin, interrupt will occur whenever falling edge is detected
}

Header.h

#ifndef HEADER_H
#define HEADER_H
#pragma once

#define ANEMOMETER_PIN 0
#define VANE_PIN A1
#define RAIN_PIN 1  

//#define WIND_UNIT 1.492 //MPH
//#define WIND_UNIT 2.4 // km/h
#define ANEMOMETER_CONV 2.4*0.277778 // m/s
#define PLUVOMETER_CONV 0.2794*60*60 //mm/h

extern void anemometerISR();
extern void pluvometerISR();

#endif

WX.cpp

#include <Arduino.h>
#include "sensors.h"
#include "header.h"

//functions go here

WX.h

extern volatile unsigned long sTime = 0; //stores startRev time for wind speed calculation
extern volatile float pulseTime = 0; //stores time between one anemomter relay closing and the next
volatile float culPulseTime; //stores cumulative pulsetimes for averaging
extern volatile bool startRev = true; //tracks when a new anemometer measurement startRevs
extern volatile unsigned int avgWindCount = 0; //stores anemometer relay counts for doing average wind speed
extern volatile unsigned int pluvCount = 0; //stores anemometer relay counts for doing average wind speed  
volatile int vaneSample[51];
extern int vaneSampleSize = 51;
extern unsigned int vaneSampleIdx = 0;
unsigned int windDirBin[16];
extern unsigned long WX_Timer = 0; //used to track how often to communicate data

float windDir;

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.