Wanted to have a console for DPV, only one requirement which is having a biiiiig display for depth. Don't want to invest to much in another large screen dive computer, and the old Aladins don't have constant backlight, hence this project.
First step is to build a pressure tester for validation, which is just RO water filter housing with OPV and needle valve, many people have done this before.
First version, MCU is a Seeed Xiao SAMD21, power by 3.7v LiPo battery on a boost converrter to 5V, display is a 1.54 inch OLED, pressure sensor is just an analog fuel line pressure transducer, dirt cheap. I took out the press button of a DJI action 4 housing (go pro housing should do as well), the hole only needs to be enlarged slightly to tap a M10 thread, which fits the 1/8 NPT transducer snugly, watertight by PTFE tape and epoxy.
This works but the depth variation is too large to my liking, perhaps the 12bit ADC built in Xiao board is not high enough for this, could add a ADS1115 16bit ADC, but I don't have much physical room to play around, that transducer is massive.
Second version, going digital. Found a ROV depth sensor using MS5837 digital depth sensor board on Aliexpress (search 'ROV M10 High Precision Depth Transducer MS5837-30BA'). Again, take out the press button, drill and tap M10, thread in the sensor and seal with PTFE tape. Both Xiao and the sensor run on 3.3V so I can get rid of the boost converter, and power Xiao directly from battery to the Vcc pin, which should drop out at around 3.5V, did a burn test, fully charged battery runs for about 20 hours.
Testing, very consistent and stable reading compared to Shearwater.
Display: depth, run time (which starts automatically passing 1 meter), and battery voltage
I have also made a version with dive computer function, ZHL-16c (fixed GF of 99/99, I mean if all of my dive computers failed I'll take my chance...)
Plenty of the algorithm code on github. But implementing it means sacrificing the size of depth which is my priority. Plus the housing only has 2 buttons, one is used by depth sensor, another by power button. So no button to switch gas, which makes it useless to me.
The BT code is very simple:
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include "MS5837.h"
#define BATTERY_PIN A8 // Battery monitoring pin
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
MS5837 sensor;
float depth = 0.0;
unsigned long prevDisplayTime = 0;
unsigned long timerStartTime = 0;
bool timerStarted = false;
int minutes = 0, seconds = 0;
float batteryVoltage = 0.0;
const int numSamples = 20; // Battery voltage oversampling
void setup() {
Wire.begin();
Wire.setClock(100000); // I2C clock speed 100 kHz
analogReadResolution(12); // Internal ADC resolution 12-bit
sensor.init();
sensor.setModel(MS5837::MS5837_30BA); // 30 bar model
sensor.setFluidDensity(1020); // EN13319 density
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
display.display();
}
void loop() {
unsigned long currentTime = millis();
if (currentTime - prevDisplayTime >= 500) { // 2Hz refresh rate
sensor.read();
depth = sensor.depth();
if (depth < 0) depth = 0; // Minimum depth 0 meter
if (depth > 99.9) depth = 99.9; // Maximum depth 99.9 meters
updateTimer(depth);
batteryVoltage = getAverageBatteryVoltage();
updateDisplay(depth, minutes, batteryVoltage);
prevDisplayTime = currentTime;
}
}
// Timer starts at 1 meter
void updateTimer(float depth) {
if (depth >= 1.0 && !timerStarted) {
timerStartTime = millis();
timerStarted = true;
}
if (timerStarted) {
unsigned long elapsedTime = millis() - timerStartTime;
minutes = elapsedTime / 60000;
seconds = (elapsedTime % 60000) / 1000;
}
}
// Battery voltage averaging
float getAverageBatteryVoltage() {
long total = 0;
for (int i = 0; i < numSamples; i++) {
total += analogRead(BATTERY_PIN);
}
float averageReading = total / numSamples;
return averageReading * (3.3 / 4095.0) * 2; // 1:1 voltage divider
}
// OLED display
void updateDisplay(float depth, int minutes, float batteryVoltage) {
display.clearDisplay();
// Depth
char depthString[5];
if (depth < 10) {
snprintf(depthString, sizeof(depthString), " %.1f", depth);
} else {
snprintf(depthString, sizeof(depthString), "%.1f", depth);
}
display.setTextColor(SSD1306_WHITE);
display.setTextSize(5);
display.setCursor(6, 0);
display.print(depthString);
// Timer
char timerString[7];
snprintf(timerString, sizeof(timerString), "%3d:%02d", minutes, seconds);
display.setTextSize(2);
display.setCursor(0, 48);
display.print(timerString);
// Battery voltage
char batteryString[6];
snprintf(batteryString, sizeof(batteryString), "%.2fV", batteryVoltage);
display.setTextSize(1);
display.setCursor(SCREEN_WIDTH - 6 * strlen(batteryString), 56);
display.print(batteryString);
display.display();
}
First step is to build a pressure tester for validation, which is just RO water filter housing with OPV and needle valve, many people have done this before.
First version, MCU is a Seeed Xiao SAMD21, power by 3.7v LiPo battery on a boost converrter to 5V, display is a 1.54 inch OLED, pressure sensor is just an analog fuel line pressure transducer, dirt cheap. I took out the press button of a DJI action 4 housing (go pro housing should do as well), the hole only needs to be enlarged slightly to tap a M10 thread, which fits the 1/8 NPT transducer snugly, watertight by PTFE tape and epoxy.
This works but the depth variation is too large to my liking, perhaps the 12bit ADC built in Xiao board is not high enough for this, could add a ADS1115 16bit ADC, but I don't have much physical room to play around, that transducer is massive.
Second version, going digital. Found a ROV depth sensor using MS5837 digital depth sensor board on Aliexpress (search 'ROV M10 High Precision Depth Transducer MS5837-30BA'). Again, take out the press button, drill and tap M10, thread in the sensor and seal with PTFE tape. Both Xiao and the sensor run on 3.3V so I can get rid of the boost converter, and power Xiao directly from battery to the Vcc pin, which should drop out at around 3.5V, did a burn test, fully charged battery runs for about 20 hours.
Testing, very consistent and stable reading compared to Shearwater.
Display: depth, run time (which starts automatically passing 1 meter), and battery voltage
I have also made a version with dive computer function, ZHL-16c (fixed GF of 99/99, I mean if all of my dive computers failed I'll take my chance...)
Plenty of the algorithm code on github. But implementing it means sacrificing the size of depth which is my priority. Plus the housing only has 2 buttons, one is used by depth sensor, another by power button. So no button to switch gas, which makes it useless to me.
The BT code is very simple:
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include "MS5837.h"
#define BATTERY_PIN A8 // Battery monitoring pin
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
MS5837 sensor;
float depth = 0.0;
unsigned long prevDisplayTime = 0;
unsigned long timerStartTime = 0;
bool timerStarted = false;
int minutes = 0, seconds = 0;
float batteryVoltage = 0.0;
const int numSamples = 20; // Battery voltage oversampling
void setup() {
Wire.begin();
Wire.setClock(100000); // I2C clock speed 100 kHz
analogReadResolution(12); // Internal ADC resolution 12-bit
sensor.init();
sensor.setModel(MS5837::MS5837_30BA); // 30 bar model
sensor.setFluidDensity(1020); // EN13319 density
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
display.display();
}
void loop() {
unsigned long currentTime = millis();
if (currentTime - prevDisplayTime >= 500) { // 2Hz refresh rate
sensor.read();
depth = sensor.depth();
if (depth < 0) depth = 0; // Minimum depth 0 meter
if (depth > 99.9) depth = 99.9; // Maximum depth 99.9 meters
updateTimer(depth);
batteryVoltage = getAverageBatteryVoltage();
updateDisplay(depth, minutes, batteryVoltage);
prevDisplayTime = currentTime;
}
}
// Timer starts at 1 meter
void updateTimer(float depth) {
if (depth >= 1.0 && !timerStarted) {
timerStartTime = millis();
timerStarted = true;
}
if (timerStarted) {
unsigned long elapsedTime = millis() - timerStartTime;
minutes = elapsedTime / 60000;
seconds = (elapsedTime % 60000) / 1000;
}
}
// Battery voltage averaging
float getAverageBatteryVoltage() {
long total = 0;
for (int i = 0; i < numSamples; i++) {
total += analogRead(BATTERY_PIN);
}
float averageReading = total / numSamples;
return averageReading * (3.3 / 4095.0) * 2; // 1:1 voltage divider
}
// OLED display
void updateDisplay(float depth, int minutes, float batteryVoltage) {
display.clearDisplay();
// Depth
char depthString[5];
if (depth < 10) {
snprintf(depthString, sizeof(depthString), " %.1f", depth);
} else {
snprintf(depthString, sizeof(depthString), "%.1f", depth);
}
display.setTextColor(SSD1306_WHITE);
display.setTextSize(5);
display.setCursor(6, 0);
display.print(depthString);
// Timer
char timerString[7];
snprintf(timerString, sizeof(timerString), "%3d:%02d", minutes, seconds);
display.setTextSize(2);
display.setCursor(0, 48);
display.print(timerString);
// Battery voltage
char batteryString[6];
snprintf(batteryString, sizeof(batteryString), "%.2fV", batteryVoltage);
display.setTextSize(1);
display.setCursor(SCREEN_WIDTH - 6 * strlen(batteryString), 56);
display.print(batteryString);
display.display();
}