Smart Buoy - [Making Wave and Temperature Measurements]
by t3chflicks in Circuits > Electronics
4980 Views, 15 Favorites, 0 Comments
Smart Buoy - [Making Wave and Temperature Measurements]
This Smart Buoy series charts our (ambitious) attempt to build a scientific buoy that can take meaningful measurements about the sea using off-the-shelf products. This is tutorial one of four. If you need a swift introduction to the project, check out our summary.
In this tutorial, we show you how we took wave and temperature measurements on our Smart Buoy.
Supplies
GY-86: Accelerometer & Gyroscope
The main sensor on the buoy is a GY-86, which contains the MPU6050 accelerometer gyroscope, the HMC5883L Magnetometer and the MS5611 barometer. These sensors enable us to measure air temperature, wave height, wave period and wave energy (and wave direction if someone out there is smart enough to do the maths).
The GY-86 communicates via i2C - see the schematic. This is the code we used to access the accelerometer and gyroscope readings.
#include "Wire.h" #include "I2Cdev.h" #include "MPU6050.h" MPU6050 mpu; int16_t ax, ay, az; int16_t gx, gy, gz; void setup() { Serial.begin(115200); Wire.begin(); Serial.println("Initializing MP6050..."); mpu.initialize(); Serial.println(mpu.testConnection() ? "MPU6050 connection successful" : "MPU6050 connection failed");} void loop() { mpu.getMotion6(&ax, &ay, &az, &gx, &gy, &gz); printResults(); delay(10); } void printResults(){ Serial.print("Acc (x,y,z) m/s/s: "); Serial.print(ax); Serial.print(" "); Serial.print(ay); Serial.print(" "); Serial.print(az); Serial.print(" Gyro(x,y,z) °/s: "); Serial.print(gx); Serial.print(" "); Serial.print(gy); Serial.print(" "); Serial.println(gz); }
Downloads
GY-86: Magnetometer (Compass)
The GY-86 contains a magnetometer which we use as a compass. You don’t need to change any connections - woo! Here’s the code to get the compass value
Remember to use the value of declination specific for your location otherwise your results may be inaccurate.
#include "Wire.h" #include "I2Cdev.h" #include "MPU6050.h" #include "HMC5883L.h" MPU6050 mpu; HMC5883L mag; int16_t mx, my, mz; // Find the declination where you are from http://www.magnetic-declination.com // -14° 59' float declination = (-14.0 - (59.0 / 60.0)) * (PI/180); void setup() { Serial.begin(115200); Wire.begin(); Serial.println("Initializing I2C devices..."); mpu.setI2CMasterModeEnabled(false); mpu.setI2CBypassEnabled(true) ; mpu.setSleepEnabled(false); mpu.initialize(); mag.initialize(); Serial.println(mpu.testConnection() ? "MPU6050 connection successful" : "MPU6050 connection failed"); Serial.println(mag.testConnection() ? "HMC5883L connection successful" : "HMC5883L connection failed"); } void loop() { mag.getHeading(&mx, &my, &mz); printResults(); delay(200); } void printResults(){ float heading = atan2(my, mx); heading += declination; if (heading < 0) heading += 2 * PI; if (heading > 2 * PI) heading -= 2 * PI; heading *= 180/M_PI; Serial.print("Heading °:"); Serial.println(heading); }
Downloads
GY-86: Barometer and Temperature
Here’s how we got the values from the MS5611 barometer.
#include "Wire.h" #include "I2Cdev.h" #include "MPU6050.h" #include <ms5611.h> MPU6050 mpu; MS5611 baro; double temp; long pressure; double altitude; void setup() { Serial.begin(115200); Wire.begin(); Serial.println("Initializing I2C devices..."); mpu.setI2CMasterModeEnabled(false); mpu.setI2CBypassEnabled(true) ; mpu.setSleepEnabled(false); mpu.initialize(); Serial.println(mpu.testConnection() ? "MPU6050 connection successful" : "MPU6050 connection failed"); while (!baro.begin(MS5611_ULTRA_HIGH_RES)) { Serial.println("MS5611 connection unsuccessful, check wiring!"); delay(500); } } void loop() { temp = baro.readTemperature(true); pressure = baro.readPressure(true); altitude = baro.getAltitude(pressure); printResults(); delay(200); } void printResults(){ Serial.print("Pressure hPa: "); Serial.print(pressure); Serial.print(" Altitude m: "); Serial.print(altitude); float tempC = temp / 340.00 + 36.53; Serial.print(" Temp °C: "); Serial.println(tempC); }
Downloads
Crude Wave Height
Once all those sensors are set up, you can use them to start measuring wave properties.
Starting with wave height...the altitude of the buoy can be calculated using the air pressure value. We can use the relative altitude of the buoy over a time period to work out the wave height like this:
#include "Wire.h" #include "I2Cdev.h" #include "MPU6050.h" // ^ #include <ms5611.h> // MPU6050 mpu; MS5611 baro; long pressure; double altitude, min_height, max_height, wave_height; void setup() { Serial.begin(115200); Wire.begin(); Serial.println("Initializing I2C devices..."); mpu.setI2CMasterModeEnabled(false); mpu.setI2CBypassEnabled(true) ; mpu.setSleepEnabled(false); mpu.initialize(); Serial.println(mpu.testConnection() ? "MPU6050 connection successful" : "MPU6050 connection failed"); while (!baro.begin(MS5611_ULTRA_HIGH_RES)) { Serial.println("MS5611 connection unsuccessful, check wiring!"); delay(500); } } void loop() { unsigned long start_time = millis(); pressure = baro.readPressure(true); altitude = baro.getAltitude(pressure); max_height = altitude; min_height = altitude; // for 15 seconds while(millis() - start_time < 15000){ pressure = baro.readPressure(true); altitude = baro.getAltitude(pressure); if (altitude < min_height) min_height = altitude; if (altitude > max_height) max_height = altitude; } wave_height = (max_height - min_height)/2.0; Serial.print("Wave Height m: "); Serial.println(wave_height); }
Downloads
Crude Wave Period
The next thing is wave period. We used airy wave theory - a lovely basic wave theory which says that water surface waves can be described as sine waves. It’s great to use because it makes the maths easier, but it is of course a simplification and does have certain caveats, including that it is inaccurate in shallower water where waves transform and break. Using this theory, we should be able to get a value of wave period using the midpoint between our maximum and minimum relative altitudes and finding how frequently the buoy crosses this point.
#include "Wire.h" #include "I2Cdev.h" #include "MPU6050.h" #include <ms5611.h> MPU6050 mpu; MS5611 baro; long pressure; double altitude, min_height, max_height, mid_point, smudge_factor; byte escaped, started; unsigned long period_start, period_end; float avg_period = -1; void setup() { Serial.begin(115200); Wire.begin(); Serial.println("Initializing I2C devices..."); mpu.setI2CMasterModeEnabled(false); mpu.setI2CBypassEnabled(true) ; mpu.setSleepEnabled(false); mpu.initialize(); Serial.println(mpu.testConnection() ? "MPU6050 connection successful" : "MPU6050 connection failed"); while (!baro.begin(MS5611_ULTRA_HIGH_RES)) { Serial.println("MS5611 connection unsuccessful, check wiring!"); delay(500); } } void loop() { unsigned long start_time = millis(); pressure = baro.readPressure(true); altitude = baro.getAltitude(pressure); max_height = altitude; min_height = altitude; // for 15 seconds while(millis() - start_time < 15000){ pressure = baro.readPressure(true); altitude = baro.getAltitude(pressure); if (altitude < min_height) min_height = altitude; if (altitude > max_height) max_height = altitude; } mid_point = (max_height + min_height)/2.0; smudge_factor = (max_height - mid_point)*0.15; Serial.print("Mid Point m: "); Serial.print(mid_point); start_time = millis(); // for 15 seconds while(millis() - start_time < 15000){ pressure = baro.readPressure(true); altitude = baro.getAltitude(pressure); // if within a range of 30% of wave height from the mid point // start the timer else stop it if (altitude < mid_point + smudge_factor && altitude > mid_point - smudge_factor){ if ( !started){ period_start = millis(); started = true; } else{ if ( escaped ){ // if it has started and escaped and is now returning period_end = millis(); started = false; escaped = false; // if the variable has already been assigned // use its previous value and new value to work out avg if(avg_period != -1){ avg_period = (avg_period + (period_end-period_start)*2) / 2.0; } // assign it else{ avg_period = (period_end-period_start)*2; } } } } else{ escaped = true; } } Serial.print(" Period s: "); Serial.println(avg_period/1000); }
Downloads
Wave Direction (HELP US)
We never actually managed to measure wave direction.
However the sensors aboard the buoy should be able to do it. You are able to measure dynamic accelerations with the GY-86 Accelerometer by subtracting the affects of the gravitational force. You are also able to get the direction of this acceleration. Using the direction of buoy acceleration along with the compass value for a given position should allow you to determine wave direction. We gave it a go in the code below.
---- If someone out there could help us with this it would be amazing <3 ----
#include "I2Cdev.h" #include "MPU6050_6Axis_MotionApps20.h" #include "HMC5883L.h" #include "Wire.h" MPU6050 mpu; HMC5883L mag; int16_t mx, my, mz; #define INTERRUPT_PIN 2 // use pin 2 on Arduino Uno & most boards float declination = -14.0 - (59.0 / 60.0); // MPU control/status vars bool dmpReady = false; // set true if DMP init was successful uint8_t mpuIntStatus; // holds actual interrupt status byte from MPU uint8_t devStatus; // return status after each device operation (0 = success, !0 = error) uint16_t packetSize; // expected DMP packet size (default is 42 bytes) uint16_t fifoCount; // count of all bytes currently in FIFO uint8_t fifoBuffer[64]; // FIFO storage buffer // orientation/motion vars Quaternion q; // [w, x, y, z] quaternion container VectorInt16 aa; // [x, y, z] accel sensor measurements VectorInt16 aaReal; // [x, y, z] gravity-free accel sensor measurements VectorInt16 aaWorld; // [x, y, z] world-frame accel sensor measurements VectorFloat gravity; // [x, y, z] gravity vector volatile bool mpuInterrupt = false; // indicates whether MPU interrupt pin has gone high void dmpDataReady() { mpuInterrupt = true; } void setup() { Serial.begin(115200); Wire.begin(); Wire.setClock(400000); mpu.setI2CMasterModeEnabled(false); mpu.setI2CBypassEnabled(true) ; mpu.setSleepEnabled(false); mpu.initialize(); mag.initialize(); // initialize device Serial.println(F("Initializing I2C devices...")); mpu.initialize(); pinMode(INTERRUPT_PIN, INPUT); Serial.println(mpu.testConnection() ? F("MPU6050 connection successful") : F("MPU6050 connection failed")); devStatus = mpu.dmpInitialize(); // supply your own gyro offsets here, scaled for min sensitivity mpu.setXGyroOffset(112); mpu.setYGyroOffset(5); mpu.setZGyroOffset(-15); mpu.setZAccelOffset(1487); if (devStatus == 0) { mpu.setDMPEnabled(true); Serial.print(digitalPinToInterrupt(INTERRUPT_PIN)); attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), dmpDataReady, RISING); mpuIntStatus = mpu.getIntStatus(); dmpReady = true; packetSize = mpu.dmpGetFIFOPacketSize(); } } void loop() { if (!dmpReady) return; while (!mpuInterrupt && fifoCount < packetSize) { if (mpuInterrupt && fifoCount < packetSize) { fifoCount = mpu.getFIFOCount(); } } mpuInterrupt = false; mpuIntStatus = mpu.getIntStatus(); fifoCount = mpu.getFIFOCount(); if ((mpuIntStatus & _BV(MPU6050_INTERRUPT_FIFO_OFLOW_BIT)) || fifoCount >= 1024) { mpu.resetFIFO(); fifoCount = mpu.getFIFOCount(); } else if (mpuIntStatus & _BV(MPU6050_INTERRUPT_DMP_INT_BIT)) { while (fifoCount < packetSize) fifoCount = mpu.getFIFOCount(); mpu.getFIFOBytes(fifoBuffer, packetSize); fifoCount -= packetSize; // OUTPUT_READABLE_WORLDACCEL mpu.dmpGetQuaternion(&q, fifoBuffer); mpu.dmpGetAccel(&aa, fifoBuffer); mpu.dmpGetGravity(&gravity, &q); mpu.dmpGetLinearAccel(&aaReal, &aa, &gravity); mpu.dmpGetLinearAccelInWorld(&aaWorld, &aaReal, &q); Serial.print("Acceleration (x,y) m/s/s: "); Serial.print(aaWorld.x); Serial.print(" "); Serial.println(aaWorld.y); // Find the angle of acceleration // double angle_of_acc = atan2(aaWorld.y, aaWorld.x); // angle_of_acc *= 180/M_PI; // Get compass heading // mag.getHeading(&mx, &my, &mz); // float heading = atan2(my, mx); // if (heading < 0) heading += 2 * PI; // if (heading > 2 * PI) heading -= 2 * PI; // heading *= 180/M_PI; // heading += declination; // Offset angle of acceleration by the heading angle // wave_direction = angle_off_acc + heading } }
Crude Wave Energy
One definition of Wave energy is defined by:
P = 0.5 height ** 2 * period [ https://en.wikipedia.org/wiki/Wave_power].
Using our values for wave period and height, we are able to calculate wave energy like this:
#include "Wire.h" #include "I2Cdev.h" #include "MPU6050.h" #include <ms5611.h> MPU6050 mpu; MS5611 baro; long pressure; double altitude, min_height, max_height, wave_height, mid_point, smudge_factor, wave_power; byte escaped, started; unsigned long period_start, period_end; float avg_period = -1; void setup() { Serial.begin(115200); Wire.begin(); Serial.println("Initializing I2C devices..."); mpu.setI2CMasterModeEnabled(false); mpu.setI2CBypassEnabled(true) ; mpu.setSleepEnabled(false); mpu.initialize(); Serial.println(mpu.testConnection() ? "MPU6050 connection successful" : "MPU6050 connection failed"); while (!baro.begin(MS5611_ULTRA_HIGH_RES)) { Serial.println("MS5611 connection unsuccessful, check wiring!"); delay(500); } } void loop() { unsigned long start_time = millis(); pressure = baro.readPressure(true); altitude = baro.getAltitude(pressure); max_height = altitude; min_height = altitude; // for 15 seconds while(millis() - start_time < 15000){ pressure = baro.readPressure(true); altitude = baro.getAltitude(pressure); if (altitude < min_height) min_height = altitude; if (altitude > max_height) max_height = altitude; } mid_point = (max_height + min_height)/2.0; wave_height = (max_height - mid_point) / 2.0; smudge_factor = wave_height * 0.15; Serial.print("Wave Height m: "); Serial.print(wave_height); start_time = millis(); // for 15 seconds while(millis() - start_time < 15000){ pressure = baro.readPressure(true); altitude = baro.getAltitude(pressure); // if within a range of 30% of wave height from the mid point // start the timer else stop it if (altitude < mid_point + smudge_factor && altitude > mid_point - smudge_factor){ if ( !started){ period_start = millis(); started = true; } else{ if ( escaped ){ // if it has started and escaped and is now returning period_end = millis(); started = false; escaped = false; // if the variable has already been assigned // use its previous value and new value to work out avg if(avg_period != -1){ avg_period = (avg_period + (period_end-period_start)*2) / 2.0; } // assign it else{ avg_period = (period_end-period_start)*2; } } } } else{ escaped = true; } } Serial.print(" Period s: "); Serial.print(avg_period/1000); wave_power = (0.5 * wave_height * wave_height * avg_period / 1000); Serial.print(" Wave Power kW/m: "); Serial.println(wave_power); }
Water Temperature
Finally, we want to be able to measure water temperature. The DS18B20 water temperature sensor will protrude from the bottom of the buoy into the sea and the Arduino communicates with it using 1-wire.
The only modification you need to make is adding a pull-up resistor on the signal wire and then we can talk to it like this:
#include <onewire.h> #include <dallastemperature.h> OneWire oneWire(6); DallasTemperature sensors(&oneWire); float water_temperature; void setup() { Serial.begin(115200); sensors.begin(); } void loop() { sensors.requestTemperatures(); water_temperature = sensors.getTempCByIndex(0); Serial.print("Water temperature C "); Serial.println(water_temperature); delay(200); }
Thanks
Thanks for reading this tutorial, this has been the first step in building our smart buoy. Check out our next tutorial soon to see how we send and store data and use the GPS.
Part 1: Making Wave And Temperature Measurement
Part 2: GPS NRF24 Radio and SD Card
Part 3: Scheduling Power to the Buoy
Check out T3chFlicks.org for more tech-focused educational content (YouTube, Instagram, Facebook, Twitter).