This forum uses cookies
This forum makes use of cookies to store your login information if you are registered, and your last visit if you are not. Cookies are small text documents stored on your computer; the cookies set by this forum can only be used on this website and pose no security risk. Cookies on this forum also track the specific topics you have read and when you last read them. Please confirm whether you accept or reject these cookies being set.

A cookie will be stored in your browser regardless of choice to prevent you being asked this question again. You will be able to change your cookie settings at any time using the link in the footer.

Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Choose a wind sensor
#21
MigGat

Thanks so much for getting back to me. The boat I'm currently on was built by me. From the bottom to the top and the finishing touch will be learning how to get some of these cheap ish instruments hooked up to my mcauther hat

It is defiantly a huge learning curve. Things are still mostly jiberish, but, I have just been throwing myself agents it every night. Most of the time I get it wrong but... open cpn is currently showing no errors.

So I need a digital to analog converter. Got it. Just checked out the hat labs website. They got some cool stuff. I think I'll go with that esp32 and send via wifi to mcarther hat. I don't have any other nmea 2000 on the boat. I'm not trying to start something that I don't have to

Is there any documentation of someone doing it this way? Or maby at least wiring up that board?
Reply
#22
You need analog to digital converter, and the most used one AFAIK is the ADS1115 (4 inputs, wire through I2C to the board).

If you get the Halmet board, it already has it. If you get any other one (SailorHat is one I use for my water tank level sensors), just make sure you add an ADS1115 and you're good to go.

Now there's the software. As I said, if you're not used to coding, i would go for SesnsESP. It basically has all the core features you'll need. You start with the template, and then just configure inputs and outputs like this:

Code:
auto* tank_level = new AnalogInput(pin, read_delay, analog_in_config_path);
tank_level->connect_to(new SKOutputNumber(sk_path));

There's plenty of docs and samples. With SensESP it would even expose a website so you can configure the relationship between analog input value and angle. Here's my OLD code for the Davis windvane. Use it only as inspiration, because SensESP has changed A LOT, and you could write this in a much simpler way.


Code:
#include <Adafruit_ADS1X15.h>
#include <Wire.h>

//#include "sensesp/sensors/analog_input.h"
#include "sensesp/sensors/digital_input.h"
#include "sensesp/sensors/sensor.h"
#include "sensesp/signalk/signalk_output.h"
#include "sensesp/system/lambda_consumer.h"
#include "sensesp/transforms/curveinterpolator.h"
#include "sensesp/transforms/linear.h"
#include "sensesp_app_builder.h"

using namespace sensesp;

class AngleInterpreter : public CurveInterpolator {
public:
  AngleInterpreter(String config_path = "")
    : CurveInterpolator(NULL, config_path) {
    // Populate a lookup table tp translate the ohm values returned by
    // our temperature sender to degrees Kelvin
    clear_samples();
    // addSample(CurveInterpolator::Sample(knownOhmValue, knownKelvin));
    add_sample(CurveInterpolator::Sample(-350, 0));
    add_sample(CurveInterpolator::Sample(20, 5));
    add_sample(CurveInterpolator::Sample(3350, 45));
    add_sample(CurveInterpolator::Sample(6950, 90));
    add_sample(CurveInterpolator::Sample(10150, 135));
    add_sample(CurveInterpolator::Sample(13650, 180));
    add_sample(CurveInterpolator::Sample(17350, 225));
    add_sample(CurveInterpolator::Sample(19900, 270));
    add_sample(CurveInterpolator::Sample(22710, 315));
    add_sample(CurveInterpolator::Sample(26000, 360));
  }
};

reactesp::ReactESP app;

Adafruit_ADS1115 ads;

// Pin connected to the ALERT/RDY signal for new sample notification.
// constexpr int READY_PIN = 17;

// This is required on ESP32 to put the ISR in IRAM. Define as
// empty for other platforms. Be careful - other platforms may have
// other requirements.
#ifndef IRAM_ATTR
#define IRAM_ATTR
#endif

// volatile bool new_data = false;
// void IRAM_ATTR NewDataReadyISR() { new_data = true; }

String awa_sk_path = "environment.wind.angleApparent";
String awa_sk_path_config_path = "/windAngleApparent/skKey";
String awa_options_config_path = "/windAngleApparent/options";

String awa_full_range_config_path = "/windAngleApparent/useFullRange";
String awa_use_average_config_path = "/windAngleApparent/useAverage";

String awa_curve_config_path = "/windAngleApparent/curve";

// GPIO number to use for the analog input
const uint8_t WIND_ANGLE_PIN = 16;  // 32;//34 inestable
// Define how often (in milliseconds) new samples are acquired
const unsigned int AWA_READ_INTERVAL = 50;
const unsigned int AWA_SEND_INTERVAL = 100;

bool _default_FullRange = false;
bool _default_UseAverage = true;

double angleToSend = 0;

int lastReading = -1;
const int READ_BUFFER_SIZE = 10;
int readBuffer[READ_BUFFER_SIZE] = {};

int readingCount = 1;
int readingCumulated = 1;

const int maxAngleValue = 26000;
const int maxAngleDifferential = 512;
const double maxAnglePossibleValue = 26350.0f;

int getReadingAverage() { return readingCumulated / readingCount; }
void resetReadingCount() {
  readingCount = 1;
  readingCumulated = 1;
}

int averagedValue(int lastValues[], int size, int newValue) {
  int total = 0;
  // Discard the first value and move all elements to the left
  for (int i = 0; i < size - 1; i++) {
    lastValues[i] = lastValues[i + 1];
    total += lastValues[i];
  }

  // Add the new value to the right
  lastValues[size - 1] = newValue;
  total += newValue;

  return total / size;
}

void SetupDavisAngle() {
  SKMetadata* metadata = new SKMetadata();
  metadata->units_ = "rad";
  metadata->description_ = "Aparent Wind Angle";
  metadata->display_name_ = "Aparent Wind Angle";
  metadata->short_name_ = "AWA";

  auto awaOutput =
    new SKOutputFloat(awa_sk_path, awa_sk_path_config_path, metadata);

  /************************** READING AND STORAGE *****************************/

  auto* readingStorage =
    new LambdaConsumer<double>([](double input) { angleToSend = input; });

  auto* windAngleReader =
    new RepeatSensor<double>(AWA_READ_INTERVAL, []() -> double {
    int reading = getReadingAverage();

    resetReadingCount();

    int raw = reading;
    int filtered = reading;

    if (reading > maxAngleValue) filtered = maxAngleValue - reading;

    double deg = (double)filtered / maxAnglePossibleValue * 360.0f;

    Serial.printf("Raw: %d | ", raw);
    Serial.printf("Filtered: %d | Deg: %.1f | ", filtered, deg);

    if (lastReading < 0) lastReading = filtered;

    int averaged = averagedValue(readBuffer, READ_BUFFER_SIZE, filtered);

    Serial.printf("Avg: %d | ", averaged);

    int16_t result = 0;

    if (abs(lastReading - averaged) > maxAngleDifferential) {
      Serial.print("-------IGNORED-------");
      result = lastReading;
    }
    else {
      result = averaged;
    }

    Serial.println("");
    lastReading = averaged;
    return result;
  });

  /******************************** SENDING ***********************************/
  auto rangeConverterFunction = [](double angle, bool fullRange) -> float {
    if (!fullRange && angle > 180) {
      angle -= 360;
    }
    return angle;
  };

  auto* rangeConverter = new LambdaTransform<float, float, bool>(
    rangeConverterFunction, _default_FullRange,
    new ParamInfo[1]{ {"fullRange", "Use Full 360 Range"} },
    awa_options_config_path);

  auto* radianConverter = new LambdaTransform<double, float>(
    [](double deg) -> float { return deg / 180.0f * PI; });

  auto* windAngleSender = new RepeatSensor<double>(
    AWA_SEND_INTERVAL, []() -> double { return angleToSend; });

  auto* logger = new LambdaConsumer<double>([](double angleToSend) {
    Serial.printf("Reads: %d\r\n", getReadingAverage());
    resetReadingCount();
    });

  windAngleReader->connect_to(new AngleInterpreter(awa_curve_config_path))
    ->connect_to(rangeConverter)
    ->connect_to(radianConverter)
    ->connect_to(readingStorage);

  windAngleSender->connect_to(awaOutput);
  // windAngleSender->connect_to(logger);
}

String aws_sk_path = "environment.wind.speedApparent";
String aws_delay_config_path = "/windSpeedApparent/readInterval";
String aws_sk_path_config_path = "/windSpeedApparent/skKey";

const uint8_t WIND_SPEED_PIN = 25;

unsigned int AWS_READ_DELAY = 1000;
unsigned int AWS_DEBOUNCE_DELAY = 10;

void SetupDavisSpeed() {
  auto* wind_speed_rev_counter = new DigitalInputDebounceCounter(
    WIND_SPEED_PIN, INPUT_PULLUP, RISING, AWS_READ_DELAY, AWS_DEBOUNCE_DELAY,
    aws_delay_config_path);

  SKMetadata* metadata = new SKMetadata();
  metadata->units_ = "m/s";
  metadata->description_ = "Aparent Wind Speed";
  metadata->display_name_ = "Aparent Wind Speed";
  metadata->short_name_ = "AWS";

  auto awsOutput =
    new SKOutputFloat(aws_sk_path, aws_sk_path_config_path, metadata);

  auto speed_detector_function = [](int input) -> float {
    // mph = P * (2.25 / I)  Where P is the pulse count, and I is the interval
    // in SECONDS (i.e: AWS_READ_DELAY / 1000)
    float mph = input * (2.25f);
    float m_s = mph * 0.44704;

    return m_s;
  };

  auto* directionDetector =
    new LambdaTransform<int, float>(speed_detector_function);

  wind_speed_rev_counter->connect_to(directionDetector)->connect_to(awsOutput);
}

/****************  ADS1115 ***********************/

constexpr int READY_PIN = 17;
volatile bool new_data = false;
void IRAM_ATTR NewDataReadyISR() { new_data = true; }

void setupADC() {
  pinMode(READY_PIN, INPUT);
  // We get a falling edge every time a new sample is ready.
  //attachInterrupt(digitalPinToInterrupt(READY_PIN), NewDataReadyISR, FALLING);

  // Start continuous conversions.
  ads.startADCReading(ADS1X15_REG_CONFIG_MUX_SINGLE_0, /*continuous=*/true);
}

void loopADC() {
  // If we don't have new data, skip this iteration.
  if (!new_data) {
    return;
  }

  int16_t reading = ads.getLastConversionResults();

  readingCount++;
  readingCumulated += reading;

  new_data = false;
}

/*************************************************/

// The setup function performs one-time application initialization.
void setup() {
#ifndef SERIAL_DEBUG_DISABLED
  SetupSerialDebug(115200);
#endif

  // Construct the global SensESPApp() object
  SensESPAppBuilder builder;
  sensesp_app = (&builder)
    // Set a custom hostname for the app.
    ->set_hostname("WindESP")
    // Optionally, hard-code the WiFi and Signal K server
    // settings. This is normally not needed.
    //->set_wifi("openplotter", the password")
    ->set_sk_server("10.10.10.1", 3000)
    ->get_app();

  /************************** DAVIS *******************************/
  SetupDavisSpeed();
  SetupDavisAngle();
  /****************************************************************/

  ads.setGain(GAIN_ONE);
  ads.setDataRate(RATE_ADS1115_860SPS);

  if (!ads.begin()) {
    Serial.println("Failed to initialize ADS.");
    while (1)
      ;
  }

  setupADC();

  app.onInterrupt(READY_PIN, INPUT, NewDataReadyISR);

  // Start networking, SK server connections and other SensESP internals
  sensesp_app->start();
}

void loop() {
  loopADC();
  app.tick();
}


As for the wiring, is actually quite simple. If you go with builtin analog and digital inputs, just plug the wires there. If I recall correctly, Davis has 1 wire for speed (digital), 1 for angle (analog), and 2 for +-.

If you have and external ADC, just wire the analog pin to one of the ADC's input, and the 4 wires for I2C to the ESP32 (check your board for pin numbers). The speed wire can go to any ESP32 input.

BTW, if you're just "playing around", you don't even need the ADS1115: ESP32 already has a couple analog input. Just that the ADC is pure garbage, so wind angle information won't be very reliable.

Oh, also make sure you use the proper resistances between analog and 3.3v, and same for speed (i think this is discussed at some point in this thread)
Reply
#23
MigGat 

This sounds like a fun project to learn something new.  Definitely have some reading to do, but i am very excited.

I will order that halmet board and take a look at the software SensESP. I'll get back to ya when I get it perfect the the first time.

Thanks for getting me started. I really appreciate the support.
Reply
#24
Awesome, have fun!
Reply


Forum Jump:


Users browsing this thread: 2 Guest(s)