Issue with variable scope

As part of my thesis work, I am trying to program an Arduino to do something that I am beginning to worry will be relatively complicated. The device itself is just an Arduino Uno R3 with an attached Adafruit TSL2561 light intensity sensor, and three white LEDs.

I need this device to record light intensity at the surface of a body of water, and then again when it reaches the bottom, and then use those variables to calculate its depth. It will then need to use that depth to calculate what the light intensity WOULD be at that depth given a hypothetical surface light intensity, and then maintain that light intensity by activating a set of LEDs when ambient light intensity falls below that threshold.

I've managed to get the mathematical operations worked out on my own, but the trouble is that I am now trying to call these functions used to initially calculate depth and intensity within loop(), which means the device is perpetually trying to recalculate depth based on a light intensity that is not changing, fooling itself into thinking that its depth is 0m, and then emitting too much light.

I have tried using flag variables (i.e. bool alreadyRun = false, etc.), but the problem is that, insofar as I have seen, float variables (which I used for depth and light intensity) can't be called outside the scope of an if statement, let alone a separate function.

Effectively, I need the device to calculate its depth and the light intensity it should maintain ONCE, but I need to be able to apply the results of these calculations in loop(). Does anyone have any suggestions as to how I might accomplish this?

You'll need some sort of PID controller. Arduino Playground - PIDLibrary

Vaethas:
but the problem is that, insofar as I have seen, float variables (which I used for depth and light intensity) can't be called outside the scope of an if statement, let alone a separate function.

There's nothing special about float variables. Like all variables, you can give them whatever scope you need. Make them global if you have to.

I'm not sure I can follow the rest of your explanation. Show us your code (use code tags) and it'll probably be easier for people to help.

byte x = 0;
void setup()
{
}

void loop()
{
 byte y;

 if(x==0)
 {
   byte z = 3;
 }
}

You can use any type instead of byte.

x has global scope and can be accessed anywhere.
y is known in loop.
z is only known inside the if block.

You want to do an initial reading; you can use a global variable or a static local variable (below uses the latter).

void loop()
{
  static bool isInitialised = false;

  if (isInitialised == false)
  {
    ...
    ...
    isInitialised = true;
  }
  ...
  ...
}
  static bool isInitialised == false;

This will make isInitialised true initially.

Disclaimer: I am well aware that my work is atrocious.

#include <Math.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_TSL2561_U.h>

const float Euler = 2.718282; //float only allows for 6 decimal places
const float k = 0.035; //attenuation coefficient
const float I0L = 30; //Simulated light intensity at surface (in lux).

Adafruit_TSL2561_Unified tsl = Adafruit_TSL2561_Unified(TSL2561_ADDR_FLOAT, 12345);

void displaySensorDetails(){
  
  sensor_t sensor;
  tsl.getSensor(&sensor);
  Serial.println("------------------------------------");
  Serial.print  ("Sensor:       "); Serial.println(sensor.name);
  Serial.print  ("Driver Ver:   "); Serial.println(sensor.version);
  Serial.print  ("Unique ID:    "); Serial.println(sensor.sensor_id);
  Serial.print  ("Max Value:    "); Serial.print(sensor.max_value); Serial.println(" lux");
  Serial.print  ("Min Value:    "); Serial.print(sensor.min_value); Serial.println(" lux");
  Serial.print  ("Resolution:   "); Serial.print(sensor.resolution); Serial.println(" lux");  
  Serial.println("------------------------------------");
  Serial.println("");
  delay(500);
  
}

void configureSensor(){
  
  /* You can also manually set the gain or enable auto-gain support */
  // tsl.setGain(TSL2561_GAIN_1X);      /* No gain ... use in bright light to avoid sensor saturation */
  // tsl.setGain(TSL2561_GAIN_16X);     /* 16x gain ... use in low light to boost sensitivity */
  tsl.enableAutoRange(true);            /* Auto-gain ... switches automatically between 1x and 16x */
  
  /* Changing the integration time gives you better sensor resolution (402ms = 16-bit data) */
  // tsl.setIntegrationTime(TSL2561_INTEGRATIONTIME_13MS);      /* fast but low resolution */
  // tsl.setIntegrationTime(TSL2561_INTEGRATIONTIME_101MS);  /* medium resolution and speed   */
  tsl.setIntegrationTime(TSL2561_INTEGRATIONTIME_402MS);  /* 16-bit data but slowest conversions */

  /* Update these values depending on what you've set above! */  
  Serial.println("------------------------------------");
  Serial.print  ("Gain:         "); Serial.println("Auto");
  Serial.print  ("Timing:       "); Serial.println("402 ms");
  Serial.println("------------------------------------");
  
}

float calculateDepth(){
    
  //Get surface light intensity
  sensors_event_t surface;
  tsl.getEvent(&surface);
  int I0D = surface.light;
    
  //Wait until device is installed
  delay(5000); //Time (ms) device will wait for me to put it in place.
    
  //Get depth light intensity
  sensors_event_t depth;
  tsl.getEvent(&depth);
  int IDD = depth.light;
    
  //Calculate depth based on light intensity values
  float D = (log(IDD/I0D) * -1)/k;

  return D;

}

float calculateLightIntensity(){

  //Call depth from calculateDepth() function.
  float D = calculateDepth();

  //Consolidate -(kD) to a single float for use with pow() function.
  float kD = (k * D) * -1;

  //Calculate light intensity to maintain
  float IDL = I0L * pow(Euler, kD);
  return IDL;

}

void setup() {

  Serial.begin(9600);
  Serial.println("PROTOTYPE TEST"); Serial.println("");

  if(!tsl.begin()){
    Serial.print("ERROR: TSL2561 NOT DETECTED.");
    while(1);
  }

  displaySensorDetails();

  configureSensor();

  Serial.println("");

}

void loop() {

  float i = calculateLightIntensity(); //This is the problem.
  
  sensors_event_t current;
  tsl.getEvent(&current);
  if(i < current.light){
    //TURN LIGHTS ON
  }
  else {
    //TURN LIGHTS OFF
  }
  
}

The trouble is in loop. My concern is that having "i" defined within that function will cause calculateDepth and calculateLightIntensity to run each time loop cycles.

Oops, corrected. Thanks for noticing.

So..

You're choosing a depth, you know how much light should be at that depth so you are adding light with your LEDs to make sure you're getting enough/correct light at that level.

Did I get this right?

-jim lee

jimLee:
So..

You're choosing a depth, you know how much light should be at that depth so you are adding light with your LEDs to make sure you're getting enough/correct light at that level.

Did I get this right?

-jim lee

What I am attempting to do here is simulate the effects of light pollution/trespass from coastal settlements. In marine biology (or, in a more broad sense, limnology), we use a derivative of Beer's Law to calculate light intensity at depth, d, based on surface light intensity and light attenuation of the water, k. That is, I[d] = I[0] * e^-kD. This device, as I envision it, uses this equation twice: first with actual light levels to calculate depth, seen here...

float D = (log(IDD/I0D) * -1)/k;

... and then a second time to determine what light intensity at depth d would be if the surface light intensity was 30 lux.

float IDL = 30 * pow(e,kD);

This hypothetical light intensity, IDL, would change depending on the depth at which the device is placed, so I need it to be able to run these calculations to determine what that level should be. Then, in the loop function, I want the device to continually check the ambient light level. If the ambient light level drops below IDL, LEDs turn on and maintain that sort of baseline light intensity (i.e. streetlights turning on at night). During the day, light intensity will be higher than that, so I don't need the LEDs to be on.

EDIT: The trouble is that the initial depth calculation requires the device to be physically held at the surface to take its first reading, and then it waits long enough for it to reach the bottom before taking a second reading. If it repeats this step after I let it sink, it will take two readings while at the bottom, notice no change in light intensity, and assume that D = 0. D is then used to calculate the threshold at which to turn the LEDs on, but since D is 0, it does so at a full 30 lux, which is far too bright.

If it's not already abundantly clear, everything written here so far is the result of a lot of hard googling in the past week or so. Saying that my background in this sort of thing is limited would be an understatement. I apologize if I'm not communicating clearly.

You need to only set I0D from in setup(), not every time calculateDepth() is called.

I0D needs to be a global or static variable.

  delay(5000); //Time (ms) device will wait for me to put it in place.

Surely you need to make the device do what the comment says rather than use a fixed delay. A single external input could be used to take the reading on the surface then to take the reading on the bottom after the device was put into place. Two inputs would make it even easier. It would be easy to arrange for a waterproof switch to be used for this purpose.

MarkT:
You need to only set I0D from in setup(), not every time calculateDepth() is called.

I0D needs to be a global or static variable.

Setting it as a static variable would, in effect, retain that initial surface reading, right? If so, setting both I0D and IDD as static variables would basically solve the problem. It wouldn't prevent the function from running, but at the very least it would prevent the result from changing. Thank you for that.

UKHeliBob:

  delay(5000); //Time (ms) device will wait for me to put it in place.

Surely you need to make the device do what the comment says rather than use a fixed delay. A single external input could be used to take the reading on the surface then to take the reading on the bottom after the device was put into place. Two inputs would make it even easier. It would be easy to arrange for a waterproof switch to be used for this purpose.

To be honest, the fixed delay is a placeholder. I would prefer to do something like what you've suggested, but I'm limited by things like grant money and deadlines to submit prototypes to satisfy said grants. I'd envisioned using some sort of Hall effect sensor to trigger the second reading without compromising the water resistance of the casing.

const float I0L = 30; //Simulated light intensity at surface (in lux).

I'm amazed at the number of people that think they need to use a float to hold an integer value.

I would prefer to do something like what you've suggested, but I'm limited by things like grant money and deadlines to submit prototypes to satisfy said grants.

It seems to me that implementing "take surface reading" and "take reading at depth" inputs is trivial. You could even have the switches above the water surface on leads to avoid any complications with waterproof switches. Presumably the on/off switch for this device is waterproof, so you have an example of how to do it already in place.

PaulS:

const float I0L = 30; //Simulated light intensity at surface (in lux).

I'm amazed at the number of people that think they need to use a float to hold an integer value.

I can understand it, since I0L is only used in the statement:

_ float IDL = I0L * pow(Euler, kD);_

I don't know if using a cast is a better solution or not in this case.

It probably does not matter, GCC is probably smart enough to go ahead and put 30.0 into the expression.

I can understand it, since I0L is only used in the statement:

float IDL = I0L * pow(Euler, kD);

What does pow return? When multiplying two different types (if two different types are used), what will be the result? Is there any difference between multiplying 3.14159 by 30 vs. multiplying it by 30.0?

PaulS:
Is there any difference between multiplying 3.14159 by 30 vs. multiplying it by 30.0?

Not enough context to answer. For the actual multiplication, no, it will be carried out in floating point. But if it is assigned to an int result, it will be cast.