Watershed Measurment Project
(dess This year in Chemestry we only had one, semester long, project. Our teacher had a plan to make a multi-year multi-class project with the goal of increasing awareness about our local watershed and eventually making the nearby creek healthy enough to support the reintroduction of a species of trout that had left it years ago. There was a lot to do so we were given several teams we could aply for. I Joined the Measurment team, which was in charge of designing and deploying a device that would measure stream flow and water temprature. Of this team of 5, Griffin Tompkins and I were charged with creating the actuall electonic component. I focused on the water flow sensor, which I created by using a watermill design on the outside of the container with magnets atatched. On the inside of the container a hall effect (Magnet) sensor would count the turns of the watermill. The count would be converted to rpm using the main arduino, which would print the information to a connected sd card. After both sensors were built, we focused on the other components. Griffin is far better at soldering than I, so I focused on the coding portion.
Around half way through the semester we encountered a problem. Although we had initially assumed that powering the sensors would be easy due to their size, furthure calculations showed that in order for the device to last 6 months (our initial goal), we would need a $4,000 milltary grade batery about half the size of a pool table. Clearly this wouldn't work, so we needed a way to get more power froma smaller battery. No one in our team new anything about batteries, so I looked into things and discovered how to increase amp hours by connecting batteries in parrallel. I Also discovered a 9v battery that had 1.2Ahs, but even with 4 connected in parrallel we still only had enough power to last a week. Fortunatly I discovered a nifty feature present in my arduino: A watchdog timer. Essentially it's a small timer that counts to 8 seconds repeatedly and runs on a different circuit. This means it can be used to turn the entire system of for most of an our, then turn it on for the last minute of every hour to collect data. It required some fancy code on my part, and took up the majority of the semester, but in the end the system could last more than long enough. Below are pictures of the finished circuit and the code used (including all the notes I wrote to the people who take up my role in the coming semesters). Also included is an animation of the exploded view of our final product made by our resident graphical artist Kieran Cunningham.
Around half way through the semester we encountered a problem. Although we had initially assumed that powering the sensors would be easy due to their size, furthure calculations showed that in order for the device to last 6 months (our initial goal), we would need a $4,000 milltary grade batery about half the size of a pool table. Clearly this wouldn't work, so we needed a way to get more power froma smaller battery. No one in our team new anything about batteries, so I looked into things and discovered how to increase amp hours by connecting batteries in parrallel. I Also discovered a 9v battery that had 1.2Ahs, but even with 4 connected in parrallel we still only had enough power to last a week. Fortunatly I discovered a nifty feature present in my arduino: A watchdog timer. Essentially it's a small timer that counts to 8 seconds repeatedly and runs on a different circuit. This means it can be used to turn the entire system of for most of an our, then turn it on for the last minute of every hour to collect data. It required some fancy code on my part, and took up the majority of the semester, but in the end the system could last more than long enough. Below are pictures of the finished circuit and the code used (including all the notes I wrote to the people who take up my role in the coming semesters). Also included is an animation of the exploded view of our final product made by our resident graphical artist Kieran Cunningham.
|
|
/*
* Sketch for arduino sensor system
* Griffin Tompkins & Nicholas Jennings - 2016.
*
* Basics of Arduino:
* THe Arduino Redbard is a handy little programable circuit. Think of it as a tiny computer.
* It isn't the most streamlined computer out there, but it comes close, so it doesn't require a lot of power.
* The basic Idea behind the code is to set all the variables up, and then take a nap. The arduino can go into power down mode
* for ~8 seconds, so it will wake up for a few milliseconds each time, then go to sleep again.
* When its slept enough, The arduino wakes up for a bit and takes measurments, then goes back to sleep.
*
* All comments with 3 dashes "///" are for use in understanding wharts going on
*/
/// Include these libraries (Sets of functions that make things easiar
#include <avr/sleep.h>
#include <avr/power.h>
#include <avr/wdt.h>
#include <OneWire.h>
#include <SPI.h>
#include <SD.h>
/// Include these variables
volatile int f_wdt=1; // the state of the arduino stored in an int
int sleeps = 0; // used to count how many short sleeps the arduino has gone through
float starttime = 0; // probably useless
int timer = 0; // probably useless
bool finished = false; // this tabs check to see if the measurment tab has finished printing data
bool longSleep = false; // if the arduino is sleeping
//measurment
File logger; // the file on the sd the arduino will print to
int DS18S20_Pin = 7; // the pin connecting to temp sensor
//Temperature chip i/o
OneWire ds(DS18S20_Pin); // telling the temp library about the pin
float mintemp; // the total value of temp this minute
unsigned int tempsInMin; // how many times the temp has been checked
//hall
int digitalPin=2; //attatch hall to pin2
boolean digitalValue=0;// variable to store the value coming from pin2
boolean prevValue=0; // used to check if the value changes
int rotations=0; // temporary rotation count used in rpm
unsigned int rpm;
unsigned long timehold; // used to derive time passed from millis
unsigned int timePassed; // time since last rpm calculation
/// the next few variables are never used, but the ide freaks out if I take them away (Same with the variables tagged "Probably useless", don't know why...
unsigned int dailyAvgRpm;
unsigned long dayTimeHold;
unsigned int dayTimePassed;
unsigned int rotInDay;
unsigned int printPerDay;
unsigned int currentAvg;
//hall
//measurment
/***************************************************
* Name: ISR(WDT_vect)
*
* Returns: Nothing.
*
* Parameters: None.
*
* Description: Basically it's setup but it's called
* every time we wake up
*
***************************************************/
ISR(WDT_vect)
{
if(f_wdt == 0)
{
f_wdt=1;
}
else
{
Serial.println("WDT Overrun!!!");
}
}
/***************************************************
* Name: enterSleep
*
* Returns: Nothing.
*
* Parameters: None.
*
* Description: Enters the arduino into sleep mode.
*
***************************************************/
void enterSleep(void)
{
set_sleep_mode(SLEEP_MODE_PWR_DOWN); /* EDIT: could also use SLEEP_MODE_PWR_DOWN or SAVE for less for lowest power consumption. */
sleep_enable();
/* Now enter sleep mode. */
sleep_mode();
/* The program will continue from here after the WDT timeout*/
sleep_disable(); /* First thing to do is disable sleep. */
/* Re-enable the peripherals. */
power_all_enable();
}
/***************************************************
* Name: setup
*
* Returns: Nothing.
*
* Parameters: None.
*
* Description: Initial Setup for all the things
*
***************************************************/
void setup()
{
Serial.begin(9600);
Serial.println("Initialising...");
delay(100); //Allow for serial print to complete.
/*** Setup the WDT ***/
/* Clear the reset flag. */
MCUSR &= ~(1<<WDRF);
/* In order to change WDE or the prescaler, we need to
* set WDCE (This will allow updates for 4 clock cycles).
*/
WDTCSR |= (1<<WDCE) | (1<<WDE);
/* set new watchdog timeout prescaler value */
WDTCSR = 1<<WDP0 | 1<<WDP3; /* 8.0 seconds */
/* Enable the WD interrupt (note no reset). */
WDTCSR |= _BV(WDIE);
// meassurment
/// setting variables to their initial values, these will change later
rotInDay = 0;
dayTimePassed = 0;
dailyAvgRpm = 0;
dayTimeHold = 0;
rpm = 0;
timehold = 0;
timePassed = 0;
pinMode(digitalPin,INPUT);//set the state of D0 as INPUT
currentAvg = 0;
mintemp = 0;
tempsInMin = 0;
if (!SD.begin(8)) {
Serial.println("SD initialization failed!");
return;
}
Serial.println("SD initialization done.");
logger = SD.open("info.txt", FILE_WRITE);
// measurment
Serial.println("Initialisation complete.");
delay(100); //Allow for serial print to complete.
}
/***************************************************
* Name: GetTemp
*
* Returns: Float
*
* Parameters: None.
*
* Description: Gets the temperature from the sensor
* /// most of this code works with the library, so it doesn't make a lot of sense. griffin just got it from the internet and it works
*
***************************************************/
float getTemp(){
byte data[12];
byte addr[8];
if ( !ds.search(addr)) {
ds.reset_search();
return -1000;
}
if ( OneWire::crc8( addr, 7) != addr[7]) {
Serial.println("CRC is not valid!");
return -1000;
}
if ( addr[0] != 0x10 && addr[0] != 0x28) {
Serial.print("Device is not recognized");
return -1000;
}
ds.reset();
ds.select(addr);
ds.write(0x44,1);
byte present = ds.reset();
ds.select(addr);
ds.write(0xBE);
for (int i = 0; i < 9; i++) {
data[i] = ds.read();
}
ds.reset_search();
byte MSB = data[1];
byte LSB = data[0];
float tempRead = ((MSB << 8) | LSB);
float TemperatureSum = tempRead / 16 * 1.8 + 32;
return TemperatureSum;
}
/***************************************************
* Name: enterSleep
*
* Returns: Nothing.
*
* Parameters: None.
*
* Description: Main application loop.
*
***************************************************/
void loop() /// every tick...
{
if (longSleep == true){ /// if we just woke up..
sleeps++; /// add 1 to our sleep counter...
Serial.print("wake up for a bit. sleeps is ");
Serial.println(sleeps);
delay(100);
if (sleeps >= 4/*set to 442 for proper sleep length@@@@@@@@@@@@@@@@@@@@@@*/){
Serial.println("exiting long sleep"); /// if we've slept long enough, wake up
delay(100);
/*delay(4000); /*finish the sleep period@@@@@@@@@@@@@@@@@@@@@@*/
timehold = millis()/1000; // reset the time
longSleep = false;
//logger.open();
logger = SD.open("info.txt", FILE_WRITE); /// open the sd to begin writing
}
}
if(f_wdt == 1 && longSleep == false) { /// if we are awake
longSleep = false;
finished = false;
Measure(); /// call the measure function, go to that tab to see more
if(finished == true){ /// if the measurment finished this tick...
/* Don't forget to clear the flag. */
logger.close(); /// close the sd to save the information
f_wdt = 0;
Serial.println("Going to long sleep");
delay(100);
sleeps = 0;
longSleep = true;
/* Re-enter sleep mode. */
enterSleep();
}
}
else
{
f_wdt = 0; //go back to sleep
enterSleep();
}
}
/***************************************************
* Name: Measure
*
* Returns: Nothing.
*
* Parameters: None.
*
* Description: Takes measurments for a minute, prints
* to the sd card, and tells 2wombo4combo
* that it is finished.
*
***************************************************/
void Measure(){
float temperature = getTemp(); /// get the tempreture using the function described in the previous tab
//Serial.println(temperature);
mintemp = mintemp + temperature; /// increase mintemp by the temp we just got
tempsInMin++; /// we just got another temp reading, so remember that
digitalValue=digitalRead(digitalPin); //read the value of pin2, 0 means magnet, 1 means no magnet
timePassed = (millis()/1000)-timehold; // calculate time passed
//dayTimePassed = (millis()/1000)-dayTimeHold;
if(digitalValue != prevValue){ //if the value changed...
if(digitalValue == 1){
//Serial.println("no magnet");
}
if(digitalValue == 0){ // ...add 1 to rotations
Serial.println("Found a magnet!");
rotations++;
}
}
if(timePassed >= 60){ // every 60 seconds
Serial.println("minute has passed"); /// print all the data we got
if(logger){
rpm = rotations/2; // the rotations become rpm, then divide by 2 because there are two magnets
//rotInDay = rotInDay + rotations; // update for daily rpm
rotations = 0;
logger.print("Info is as follows: ");
logger.print("rpm is: ");
logger.print(rpm);
//Serial.print(" total daily rotations is: ");
//Serial.print(rotInDay);
//printPerDay++; // update for daily rpm;
//Serial.print(" prints in day is: ");
//Serial.print(printPerDay);
//Serial.print(" day time passed is: ");
//Serial.print(dayTimePassed);
//currentAvg = rotInDay/printPerDay;
//Serial.print(" current Average is: ");
//Serial.print(currentAvg);
mintemp = mintemp/tempsInMin; // divide mintemp by temp readings, this is essentially finding the average
logger.print(" average temp this min: ");
logger.println(mintemp);
mintemp = 0;
tempsInMin = 0;
delay(100);
finished = true; /// tell the main tab we are finished measuring and are ready to go to sleep
}
}
prevValue = digitalValue;
}
* Sketch for arduino sensor system
* Griffin Tompkins & Nicholas Jennings - 2016.
*
* Basics of Arduino:
* THe Arduino Redbard is a handy little programable circuit. Think of it as a tiny computer.
* It isn't the most streamlined computer out there, but it comes close, so it doesn't require a lot of power.
* The basic Idea behind the code is to set all the variables up, and then take a nap. The arduino can go into power down mode
* for ~8 seconds, so it will wake up for a few milliseconds each time, then go to sleep again.
* When its slept enough, The arduino wakes up for a bit and takes measurments, then goes back to sleep.
*
* All comments with 3 dashes "///" are for use in understanding wharts going on
*/
/// Include these libraries (Sets of functions that make things easiar
#include <avr/sleep.h>
#include <avr/power.h>
#include <avr/wdt.h>
#include <OneWire.h>
#include <SPI.h>
#include <SD.h>
/// Include these variables
volatile int f_wdt=1; // the state of the arduino stored in an int
int sleeps = 0; // used to count how many short sleeps the arduino has gone through
float starttime = 0; // probably useless
int timer = 0; // probably useless
bool finished = false; // this tabs check to see if the measurment tab has finished printing data
bool longSleep = false; // if the arduino is sleeping
//measurment
File logger; // the file on the sd the arduino will print to
int DS18S20_Pin = 7; // the pin connecting to temp sensor
//Temperature chip i/o
OneWire ds(DS18S20_Pin); // telling the temp library about the pin
float mintemp; // the total value of temp this minute
unsigned int tempsInMin; // how many times the temp has been checked
//hall
int digitalPin=2; //attatch hall to pin2
boolean digitalValue=0;// variable to store the value coming from pin2
boolean prevValue=0; // used to check if the value changes
int rotations=0; // temporary rotation count used in rpm
unsigned int rpm;
unsigned long timehold; // used to derive time passed from millis
unsigned int timePassed; // time since last rpm calculation
/// the next few variables are never used, but the ide freaks out if I take them away (Same with the variables tagged "Probably useless", don't know why...
unsigned int dailyAvgRpm;
unsigned long dayTimeHold;
unsigned int dayTimePassed;
unsigned int rotInDay;
unsigned int printPerDay;
unsigned int currentAvg;
//hall
//measurment
/***************************************************
* Name: ISR(WDT_vect)
*
* Returns: Nothing.
*
* Parameters: None.
*
* Description: Basically it's setup but it's called
* every time we wake up
*
***************************************************/
ISR(WDT_vect)
{
if(f_wdt == 0)
{
f_wdt=1;
}
else
{
Serial.println("WDT Overrun!!!");
}
}
/***************************************************
* Name: enterSleep
*
* Returns: Nothing.
*
* Parameters: None.
*
* Description: Enters the arduino into sleep mode.
*
***************************************************/
void enterSleep(void)
{
set_sleep_mode(SLEEP_MODE_PWR_DOWN); /* EDIT: could also use SLEEP_MODE_PWR_DOWN or SAVE for less for lowest power consumption. */
sleep_enable();
/* Now enter sleep mode. */
sleep_mode();
/* The program will continue from here after the WDT timeout*/
sleep_disable(); /* First thing to do is disable sleep. */
/* Re-enable the peripherals. */
power_all_enable();
}
/***************************************************
* Name: setup
*
* Returns: Nothing.
*
* Parameters: None.
*
* Description: Initial Setup for all the things
*
***************************************************/
void setup()
{
Serial.begin(9600);
Serial.println("Initialising...");
delay(100); //Allow for serial print to complete.
/*** Setup the WDT ***/
/* Clear the reset flag. */
MCUSR &= ~(1<<WDRF);
/* In order to change WDE or the prescaler, we need to
* set WDCE (This will allow updates for 4 clock cycles).
*/
WDTCSR |= (1<<WDCE) | (1<<WDE);
/* set new watchdog timeout prescaler value */
WDTCSR = 1<<WDP0 | 1<<WDP3; /* 8.0 seconds */
/* Enable the WD interrupt (note no reset). */
WDTCSR |= _BV(WDIE);
// meassurment
/// setting variables to their initial values, these will change later
rotInDay = 0;
dayTimePassed = 0;
dailyAvgRpm = 0;
dayTimeHold = 0;
rpm = 0;
timehold = 0;
timePassed = 0;
pinMode(digitalPin,INPUT);//set the state of D0 as INPUT
currentAvg = 0;
mintemp = 0;
tempsInMin = 0;
if (!SD.begin(8)) {
Serial.println("SD initialization failed!");
return;
}
Serial.println("SD initialization done.");
logger = SD.open("info.txt", FILE_WRITE);
// measurment
Serial.println("Initialisation complete.");
delay(100); //Allow for serial print to complete.
}
/***************************************************
* Name: GetTemp
*
* Returns: Float
*
* Parameters: None.
*
* Description: Gets the temperature from the sensor
* /// most of this code works with the library, so it doesn't make a lot of sense. griffin just got it from the internet and it works
*
***************************************************/
float getTemp(){
byte data[12];
byte addr[8];
if ( !ds.search(addr)) {
ds.reset_search();
return -1000;
}
if ( OneWire::crc8( addr, 7) != addr[7]) {
Serial.println("CRC is not valid!");
return -1000;
}
if ( addr[0] != 0x10 && addr[0] != 0x28) {
Serial.print("Device is not recognized");
return -1000;
}
ds.reset();
ds.select(addr);
ds.write(0x44,1);
byte present = ds.reset();
ds.select(addr);
ds.write(0xBE);
for (int i = 0; i < 9; i++) {
data[i] = ds.read();
}
ds.reset_search();
byte MSB = data[1];
byte LSB = data[0];
float tempRead = ((MSB << 8) | LSB);
float TemperatureSum = tempRead / 16 * 1.8 + 32;
return TemperatureSum;
}
/***************************************************
* Name: enterSleep
*
* Returns: Nothing.
*
* Parameters: None.
*
* Description: Main application loop.
*
***************************************************/
void loop() /// every tick...
{
if (longSleep == true){ /// if we just woke up..
sleeps++; /// add 1 to our sleep counter...
Serial.print("wake up for a bit. sleeps is ");
Serial.println(sleeps);
delay(100);
if (sleeps >= 4/*set to 442 for proper sleep length@@@@@@@@@@@@@@@@@@@@@@*/){
Serial.println("exiting long sleep"); /// if we've slept long enough, wake up
delay(100);
/*delay(4000); /*finish the sleep period@@@@@@@@@@@@@@@@@@@@@@*/
timehold = millis()/1000; // reset the time
longSleep = false;
//logger.open();
logger = SD.open("info.txt", FILE_WRITE); /// open the sd to begin writing
}
}
if(f_wdt == 1 && longSleep == false) { /// if we are awake
longSleep = false;
finished = false;
Measure(); /// call the measure function, go to that tab to see more
if(finished == true){ /// if the measurment finished this tick...
/* Don't forget to clear the flag. */
logger.close(); /// close the sd to save the information
f_wdt = 0;
Serial.println("Going to long sleep");
delay(100);
sleeps = 0;
longSleep = true;
/* Re-enter sleep mode. */
enterSleep();
}
}
else
{
f_wdt = 0; //go back to sleep
enterSleep();
}
}
/***************************************************
* Name: Measure
*
* Returns: Nothing.
*
* Parameters: None.
*
* Description: Takes measurments for a minute, prints
* to the sd card, and tells 2wombo4combo
* that it is finished.
*
***************************************************/
void Measure(){
float temperature = getTemp(); /// get the tempreture using the function described in the previous tab
//Serial.println(temperature);
mintemp = mintemp + temperature; /// increase mintemp by the temp we just got
tempsInMin++; /// we just got another temp reading, so remember that
digitalValue=digitalRead(digitalPin); //read the value of pin2, 0 means magnet, 1 means no magnet
timePassed = (millis()/1000)-timehold; // calculate time passed
//dayTimePassed = (millis()/1000)-dayTimeHold;
if(digitalValue != prevValue){ //if the value changed...
if(digitalValue == 1){
//Serial.println("no magnet");
}
if(digitalValue == 0){ // ...add 1 to rotations
Serial.println("Found a magnet!");
rotations++;
}
}
if(timePassed >= 60){ // every 60 seconds
Serial.println("minute has passed"); /// print all the data we got
if(logger){
rpm = rotations/2; // the rotations become rpm, then divide by 2 because there are two magnets
//rotInDay = rotInDay + rotations; // update for daily rpm
rotations = 0;
logger.print("Info is as follows: ");
logger.print("rpm is: ");
logger.print(rpm);
//Serial.print(" total daily rotations is: ");
//Serial.print(rotInDay);
//printPerDay++; // update for daily rpm;
//Serial.print(" prints in day is: ");
//Serial.print(printPerDay);
//Serial.print(" day time passed is: ");
//Serial.print(dayTimePassed);
//currentAvg = rotInDay/printPerDay;
//Serial.print(" current Average is: ");
//Serial.print(currentAvg);
mintemp = mintemp/tempsInMin; // divide mintemp by temp readings, this is essentially finding the average
logger.print(" average temp this min: ");
logger.println(mintemp);
mintemp = 0;
tempsInMin = 0;
delay(100);
finished = true; /// tell the main tab we are finished measuring and are ready to go to sleep
}
}
prevValue = digitalValue;
}