Tide Chart LED Matrix project

It figures… issues trying to make a tide bar chart display using 2 32x8 WS2812B LED Matrix parts.

Before the addressable LED Matrix parts arrived(china) I looked for a tide chart calculator and found a nice one which would be able to generate accurate tide predictions for 10 years. Look ma, no Internet connection…
On an Arduino Nano I ran the tide-calc-test sketch and coded up a routine to generate 24 hours worth of tide heights at 45 minute intervals( 32 columns ) into an array of 32 floats(128 Bytes).

I received the two 32x8 addressable LED matrix displays and wired them up together for 64x8, connected an Arduino Nano and using Adafruit NeoMatrix library scrolled “Hello” across both.

So I added the Tide Calc code for generating 32 tide heights per day and the Nano would lock up. I tried addressing each 32x8 matrix separately and the first array scrolls while the 2nd one freezes.
I slowly started commenting out parts of the Tide Calc code and it seems just creating 2 TimeDate variables is enough to cause the lockup… It doesn’t happen if using a 32x8 matrix instead of 64x8 so likely a low memory issue.

I tried compiling for an Arduino Leonardo(Pro Micro) since it has 2.5KB of SRAM as opposed to 2KB but was surprised the Arduino compiler warned of low memory while the Nano compiled without warning. Odd.

I might have to switch a Teensy 3.2/3.5(5V tolerant) or Teensy LC which has one pin(17) which is 5v buffered and see if the libraries are supported there.

And I thought this was going to be a quick project. Ha ha.

code below, note: it freezes on initializing array of floats and if populating the array in a function:

#include <Adafruit_GFX.h>
#include <Adafruit_NeoMatrix.h>
#include <Adafruit_NeoPixel.h>

//Header files for talking to real time clock
#include <Wire.h> // Required for RTClib
#include <SPI.h> // Required for RTClib to compile properly
#include <RTClib.h> // From https://github.com/millerlp/RTClib

#define PIN 6

// Tide calculation library setup.
#include "TidelibLaJollaScrippsInstitutionWharfCalifornia.h"
// Other sites available at http://github.com/millerlp/Tide_calculator
TideCalc myTideCalc; // Create TideCalc object called myTideCalc
// using a 32x8 segment matrix display for the tide chart graph
int iChartCol=32;
float fChartData[32];

// MATRIX DECLARATION:
// Parameter 1 = width of NeoPixel matrix
// Parameter 2 = height of matrix
// Parameter 3 = pin number (most are valid)
// Parameter 4 = matrix layout flags, add together as needed:
//   NEO_MATRIX_TOP, NEO_MATRIX_BOTTOM, NEO_MATRIX_LEFT, NEO_MATRIX_RIGHT:
//     Position of the FIRST LED in the matrix; pick two, e.g.
//     NEO_MATRIX_TOP + NEO_MATRIX_LEFT for the top-left corner.
//   NEO_MATRIX_ROWS, NEO_MATRIX_COLUMNS: LEDs are arranged in horizontal
//     rows or in vertical columns, respectively; pick one or the other.
//   NEO_MATRIX_PROGRESSIVE, NEO_MATRIX_ZIGZAG: all rows/columns proceed
//     in the same order, or alternate lines reverse direction; pick one.
//   See example below for these values in action.
// Parameter 5 = pixel type flags, add together as needed:
//   NEO_KHZ800  800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
//   NEO_KHZ400  400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
//   NEO_GRB     Pixels are wired for GRB bitstream (most NeoPixel products)
//   NEO_GRBW    Pixels are wired for GRBW bitstream (RGB+W NeoPixel products)
//   NEO_RGB     Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)

Adafruit_NeoMatrix matrix = Adafruit_NeoMatrix(64, 8, PIN,
  NEO_MATRIX_TOP     + NEO_MATRIX_LEFT +
  NEO_MATRIX_COLUMNS + NEO_MATRIX_PROGRESSIVE,
  NEO_GRB            + NEO_KHZ800);

const uint16_t colors[] = {
  matrix.Color(255, 0, 0), matrix.Color(0, 255, 0), matrix.Color(0, 0, 255) };

void setup() {
  matrix.begin();
  matrix.setTextWrap(false);
  matrix.setBrightness(10);
  matrix.setTextColor(colors[0]);
  // initialize fChartData[]
  // THIS CAUSES THE DISPLAY TO FREEZE
  for( int i=0; i<iChartCol; i++ )
     fChartData[i]=0;
  getChartData(fChartData, iChartCol);
}

int x    = matrix.width();
int pass = 0;

void loop() {
  matrix.fillScreen(0);
  matrix.setCursor(x, 0);
  matrix.print(F("hello"));
  if(--x < -36) {
    x = matrix.width();
    if(++pass >= 3) pass = 0;
    matrix.setTextColor(colors[pass]);
  }
  matrix.show();
  delay(100);
}


void getChartData(float fChartData[32], int numCol) {
  int iColumns = numCol;
  DateTime today, startTime;
  //today = DateTime(__DATE__,__TIME__);
  today = DateTime(2022,3,18,13,29,0);

  startTime = DateTime(today.year(),today.month(),today.day(),00,00,0); // TODO: get today() hr=0 min=0
  // 24 hours in a day but 32 segments...
  //(24hr * 60min * 60sec)/32 // seconds/day / 32 
  for( int x=1; x <= iColumns; x++ ) {
    // this also causes the display to freeze
    ;//fChartData[x-1] = myTideCalc.currentTide(startTime.unixtime() + ((24 * 60L * 60)/iColumns) * x);
  }  
}
//*************************************```
1 Like

Looking like a memory issue that the compiler is not catching.

It was locking up if I had some DateTime structures in a function populating the array of floats and worked if I commented those lines out. I then add 2 DateTime objects back, it locks, then I reduce the display matrix size from 64x8 to 32x8 and it starts working again.

hmm, I can even create DateTime objects and not get a lockup if the matrix is 32x8 vs 64x8.

#include <Adafruit_GFX.h>
#include <Adafruit_NeoMatrix.h>
#include <Adafruit_NeoPixel.h>

//Header files for talking to real time clock
#include <Wire.h> // Required for RTClib
#include <SPI.h> // Required for RTClib to compile properly
#include <RTClib.h> // From https://github.com/millerlp/RTClib

#define PIN 6

// Tide calculation library setup.
#include "TidelibLaJollaScrippsInstitutionWharfCalifornia.h"
// Other sites available at http://github.com/millerlp/Tide_calculator
TideCalc myTideCalc; // Create TideCalc object called myTideCalc
// using a 32x8 segment matrix display for the tide chart graph
int iChartCol=32;
float fChartData[32];

// MATRIX DECLARATION:
// Parameter 1 = width of NeoPixel matrix
// Parameter 2 = height of matrix
// Parameter 3 = pin number (most are valid)
// Parameter 4 = matrix layout flags, add together as needed:
//   NEO_MATRIX_TOP, NEO_MATRIX_BOTTOM, NEO_MATRIX_LEFT, NEO_MATRIX_RIGHT:
//     Position of the FIRST LED in the matrix; pick two, e.g.
//     NEO_MATRIX_TOP + NEO_MATRIX_LEFT for the top-left corner.
//   NEO_MATRIX_ROWS, NEO_MATRIX_COLUMNS: LEDs are arranged in horizontal
//     rows or in vertical columns, respectively; pick one or the other.
//   NEO_MATRIX_PROGRESSIVE, NEO_MATRIX_ZIGZAG: all rows/columns proceed
//     in the same order, or alternate lines reverse direction; pick one.
//   See example below for these values in action.
// Parameter 5 = pixel type flags, add together as needed:
//   NEO_KHZ800  800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
//   NEO_KHZ400  400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
//   NEO_GRB     Pixels are wired for GRB bitstream (most NeoPixel products)
//   NEO_GRBW    Pixels are wired for GRBW bitstream (RGB+W NeoPixel products)
//   NEO_RGB     Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)

Adafruit_NeoMatrix matrix = Adafruit_NeoMatrix(32, 8, PIN,
  NEO_MATRIX_TOP     + NEO_MATRIX_LEFT +
  NEO_MATRIX_COLUMNS + NEO_MATRIX_PROGRESSIVE,
  NEO_GRB            + NEO_KHZ800);

const uint16_t colors[] = {
  matrix.Color(255, 0, 0), matrix.Color(0, 255, 0), matrix.Color(0, 0, 255) };

void setup() {
  matrix.begin();
  matrix.setTextWrap(false);
  matrix.setBrightness(10);
  matrix.setTextColor(colors[0]);
  // initialize fChartData[]
  // THIS CAUSES THE DISPLAY TO FREEZE
  for( int i=0; i<iChartCol; i++ )
     fChartData[i]=0;
  getChartData(fChartData, iChartCol);
  // For debugging output to serial monitor
  Serial.begin(57600); // Set baud rate to 57600 in serial monitor for slow 8MHz micros
  for( int x=1; x <= 32; x++ ) {
    Serial.print(" Tide height: ");
    Serial.print(fChartData[x-1],3);
    Serial.print(" ft.");
    Serial.println();
  }
}

int x    = matrix.width();
int pass = 0;

void loop() {
  matrix.fillScreen(0);
  matrix.setCursor(x, 0);
  matrix.print(F("hello"));
  if(--x < -36) {
    x = matrix.width();
    if(++pass >= 3) pass = 0;
    matrix.setTextColor(colors[pass]);
  }
  matrix.show();
  delay(100);
}


void getChartData(float fChartData[32], int numCol) {
  int iColumns = numCol;
  DateTime today, startTime;
  today = DateTime(__DATE__,__TIME__);
  //today = DateTime(2022,3,18,13,29,0);

  startTime = DateTime(today.year(),today.month(),today.day(),00,00,0); // TODO: get today() hr=0 min=0
  // 24 hours in a day but 32 segments...
  //(24hr * 60min * 60sec)/32 // seconds/day / 32 
  for( int x=1; x <= iColumns; x++ ) {
    // this also causes the display to freeze when using 64x8 matrix
    fChartData[x-1] = myTideCalc.currentTide(startTime.unixtime() + ((24 * 60L * 60)/iColumns) * x);
  }  
}

1 Like

Nice one maybe post a YouTube video or photo here? Well done!

2 Likes

How much code is being generated?

:smiley_cat:

I’ll post the code an a video once I get it functional but currently it looks like I might have to switch platforms off of the Arduino Nano to something like an ESP or Teensy.

So far I’ve only validated the workings of the tide calculation library and generated a days worth of data to display on 32 pixel columns and then validated the workings of the two 32x8 WS2812B LED matrices. My problem showed up once I tried to combine the two libraries and code to use the libraries. I’m thinking the Adafruit library does runtime allocations since the compiler is not complaining yet it’s looking like a memory allocation issue is causing problems.

There are some nice emulators I found for my Linux box and found them handy for these types of issues.

Monitor memory allocation and usage and the ability to stop it and ‘poke’ around in the simulated micros ‘memory’

Might see if you can locate something similar and run your code there… I searched on it and hit a bunch of them

This was much quicker and more simple than the hardware approach… or commenting out code to try and ‘guess’ where the issue is/maybe…

Good luck.

:smiley_cat:

1 Like

I found MemoryUsage library which showed Stack and Heap sizes and the data structures are just taking up too much heap space. I shrunk things down as much as I could with int8_t, rounding floats and casting to int8_t and creating consts were ever I could. Got a few more columns out of the NeoMatrix structure. Got up to 58x8 but not 64x8.

Plan B is to just use one 32x8 and graph the tide within that window. When tides hit the extremes(7ft and 0ft) I will try setting those pixel colors to indicate how far with red above 7’ and below -1’ since those are King Tide extents in this part of the world.

Time to add the RTC and start plotting.

2 Likes

I used to use those, load them, deal with batteries/holders… Now I just purchase a GPS receiver, stick it to the top and use it for a clock. Had a couple problems, but I moved the I/O to the uart. Solved all the issues.

Some limitations, but low cost & accurate…

Good luck

:smiley_cat:

1 Like

Here’s the first working version of the Tide Chart LED Matrix project using a single 32x8 WS2812b matrix.
video - tide chart using ws2812b matrix - YouTube

I round the floating point tide heights into an int8_t(byte) array.

2022/3/20
Todays tide in 45 minute increments:
 Tide height: 2.841 ft.
 Tide height: 1.909 ft.
 Tide height: 1.065 ft.
 Tide height: 0.425 ft.
 Tide height: 0.075 ft.
 Tide height: 0.057 ft.
 Tide height: 0.367 ft.
 Tide height: 0.952 ft.
 Tide height: 1.722 ft.
 Tide height: 2.559 ft.
 Tide height: 3.338 ft.
 Tide height: 3.944 ft.
 Tide height: 4.289 ft.
 Tide height: 4.327 ft.
 Tide height: 4.058 ft.
 Tide height: 3.527 ft.
 Tide height: 2.822 ft.
 Tide height: 2.053 ft.
 Tide height: 1.343 ft.
 Tide height: 0.805 ft.
 Tide height: 0.525 ft.
 Tide height: 0.552 ft.
 Tide height: 0.889 ft.
 Tide height: 1.494 ft.
 Tide height: 2.287 ft.
 Tide height: 3.156 ft.
 Tide height: 3.983 ft.
 Tide height: 4.651 ft.
 Tide height: 5.067 ft.
 Tide height: 5.170 ft.
 Tide height: 4.944 ft.
 Tide height: 4.417 ft.


#include <Adafruit_NeoPixel.h>

//Header files for talking to real time clock
#include <Wire.h> // Required for RTClib
#include <SPI.h> // Required for RTClib to compile properly
#include <RTClib.h> // From https://github.com/millerlp/RTClib


#define PIN 6

// Tide calculation library setup.
#include "TidelibLaJollaScrippsInstitutionWharfCalifornia.h"
// Other sites available at http://github.com/millerlp/Tide_calculator

// using a 32x8 segment matrix display for the tide chart graph
const int8_t iChartCol=32;
int8_t iChartData[32];
float fChartData[32];
int8_t iChartDataDec[32];

RTC_DS3231 rtc;

#define BRIGHTNESS 25 // Set BRIGHTNESS to about 1/5 (max = 255)

#define MATRIX_WIDTH  8
#define MATRIX_LENGTH 32
#define LED_COUNT 32*8
// Declare our NeoPixel strip object:
Adafruit_NeoPixel strip(LED_COUNT, PIN, NEO_GRB + NEO_KHZ800);
// Argument 1 = Number of pixels in NeoPixel strip
// Argument 2 = Arduino pin number (most are valid)
// Argument 3 = Pixel type flags, add together as needed:
//   NEO_KHZ800  800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
//   NEO_KHZ400  400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
//   NEO_GRB     Pixels are wired for GRB bitstream (most NeoPixel products)
//   NEO_RGB     Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)
//   NEO_RGBW    Pixels are wired for RGBW bitstream (NeoPixel RGBW products)


void setup() {
  if (! rtc.begin()) {
    Serial.println("Couldn't find RTC");
    Serial.flush();
    while (1) delay(10);
  }
  if (rtc.lostPower()) {
    Serial.println("RTC lost power, let's set the time!");
    // When time needs to be set on a new device, or after a power loss, the
    // following line sets the RTC to the date & time this sketch was compiled
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
    // This line sets the RTC with an explicit date & time, for example to set
    // January 21, 2014 at 3am you would call:
    // rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
  }

  // When time needs to be re-set on a previously configured device, the
  // following line sets the RTC to the date & time this sketch was compiled
  // rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  // This line sets the RTC with an explicit date & time, for example to set
  // January 21, 2014 at 3am you would call:
  // rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));

  strip.begin();           // INITIALIZE NeoPixel strip object (REQUIRED)
  strip.show();            // Turn OFF all pixels ASAP
  strip.setBrightness(BRIGHTNESS);

  // initialize fChartData[]
  for( int8_t i=0; i<iChartCol; i++ ){
     iChartData[i]=0;
     fChartData[i]=0;
     iChartDataDec[i]=0;     
  }
  // For debugging output to serial monitor
  Serial.begin(57600); // Set baud rate to 57600 in serial monitor for slow 8MHz micros

  getChartData();//iChartData, iChartCol);

  Serial.println(F("Todays tide in 45 minute increments:"));
  for( int8_t x=1; x <= iChartCol; x++ ) {
    Serial.print(F(" Tide height: "));
    //Serial.print(iChartData[x-1],3);
    Serial.print(fChartData[x-1],3);
    //Serial.print(".");Serial.print(iChartDataDec[x-1]);
    Serial.print(F(" ft."));
    Serial.println();
  }
  Serial.println();
}


void loop() {
  // check for new day and run getChartData() on newday( hour=0 )
  static int8_t iLastHour = 24;
  if( rtc.now().hour() == 0 && iLastHour != 0){ // new day
    getChartData();
    iLastHour=0;
    strip.clear();
  }
  else
    iLastHour = rtc.now().hour();
  const int8_t iSpeed = 10; //lower is faster
  // Fill along the length of the strip in various colors...
  //colorWipe(strip.Color(255,   0,   0)     , iSpeed); // Red
  timeLine(strip.Color(  0,   50, 0)     , iSpeed/5); // light Green
  tideWipe(strip.Color(  0,   0, 255)     , iSpeed); // Blue
}

// Fill strip pixels one after another with a color. Strip is NOT cleared
// first; anything there will be covered pixel by pixel. Pass in color
// (as a single 'packed' 32-bit value, which you can get by calling
// strip.Color(red, green, blue) as shown in the loop() function above),
// and a delay time (in milliseconds) between pixels.
void colorWipe(uint32_t color, int wait) {
  for(int i=0; i<strip.numPixels(); i++) { // For each pixel in strip...
    strip.setPixelColor(i, color);         //  Set pixel's color (in RAM)
    strip.show();                          //  Update strip to match
    delay(wait);                           //  Pause for a moment
  }
}
// Turn on a matrix columns pixel one after another with a color.
// Color(red, green, blue) as shown in the loop() function above),
// and a delay time (in milliseconds) between pixels.
void tideWipe(uint32_t color, int wait) {
  int8_t iCol;
  for(int i=0; i<strip.numPixels(); i++) { // For each pixel in strip...
    // check for which column
    iCol = i/MATRIX_WIDTH;
    //Serial.print("column:");Serial.println(iCol);
    if( (iCol % 2) == 0 ) // true == even
        strip.setPixelColor(7+(iCol*MATRIX_WIDTH)-iChartData[iCol], color);         //  Set pixel's color (in RAM)
    else // odd
        strip.setPixelColor((iCol*MATRIX_WIDTH)+iChartData[iCol], color);         //  Set pixel's color (in RAM)
    strip.show();                          //  Update strip to match
    delay(wait);                           //  Pause for a moment
  }
}

void timeLine(uint32_t color, int wait) {
  int8_t iCol, iTimeCol;
  int8_t iHour, iMinutes;
  static int8_t iCurCol=0;

  DateTime curTime;
  curTime = rtc.now();
  iHour = curTime.hour();
  iMinutes = curTime.minute();
  iTimeCol=abs((iHour*60+iMinutes)/45);  
  for(int i=0; i<strip.numPixels(); i++) { // For each pixel in strip...
    // check for which column
    iCol = i/MATRIX_WIDTH;
    //Serial.print("column:");Serial.println(iCol);
    if( iCol == iTimeCol ) { // true == even
      if( iCol != iCurCol ) // clear previous line
        for(int8_t j=0; j<MATRIX_WIDTH; j++)
          strip.setPixelColor(iCurCol*MATRIX_WIDTH+j, strip.Color(0, 0, 0)); // Clear pixel's
      for(int8_t j=0; j<MATRIX_WIDTH; j++)
        strip.setPixelColor(iCol*MATRIX_WIDTH+j, color);      //  Set pixel's color (in RAM)
      iCurCol=iCol;
    }
    strip.show();                          //  Update strip to match
    delay(wait);                           //  Pause for a moment
  }
}


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

void getChartData(void) {
  TideCalc myTideCalc; // Create TideCalc object called myTideCalc
  float result;
  DateTime today, startTime;
  //today = DateTime(__DATE__,__TIME__);
  //today = DateTime(2022,3,19,13,29,0);
  today = rtc.now();
  Serial.print(today.year(), DEC);
  Serial.print('/');
  Serial.print(today.month(), DEC);
  Serial.print('/');
  Serial.println(today.day(), DEC);

  startTime = DateTime(today.year(),today.month(),today.day(),00,00,0); // TODO: get today() hr=0 min=0
  // 24 hours in a day but 32 segments...
  //(24hr * 60min * 60sec)/32 // seconds/day / 32 
  for( int8_t x=1; x <= iChartCol; x++ ) {
    result = myTideCalc.currentTide(startTime.unixtime() + ((24 * 60L * 60)/iChartCol) * x);
    fChartData[x-1] = result;
    iChartData[x-1] = (int8_t)round(result);
    int whole = (int)result;
    //Serial.print(result,3);Serial.print("::");Serial.println((int8_t)abs((result-whole)*100));
    iChartDataDec[x-1] = (int8_t)abs((result-whole)*100);
  }
}
//*************************************
4 Likes

Close to final! Here is what is working:
Able to calculate 10 years of tide predictions
RTC(realtime clock) generates accurate date/time
Daily tide prediction updates occur at midnight
Cursor indicates current time( green line )
Tide graph data tests for out of bounds(greater than 7ft and less than 0 ft) situations
Annotated tide peaks( needs more edge case testing )
Annotate King Tide peaks

What’s not working:
nothing noticed so far but expect min-max peak detection to show something when peaks happen at columns 0 or 31.

TODO:
Adjust tide calc graph to remove daylight savings adjustment. Clock cursor should be on 24hr lines
Enable/disable serial output with a DEBUG flag
Add 2 buttons to move the cursor forwards or backwards in time(ie 24hr clock time adjustment).
wood cover: laser cut a piece of wood so LEDs show through holes
wood cover: laser engrave time marks and other decorations
Make a box for holding electronics and add micro-USB connector for powering

source code:

#include <Adafruit_NeoPixel.h>

//Header files for talking to real time clock
#include <Wire.h> // Required for RTClib
#include <SPI.h> // Required for RTClib to compile properly
#include <RTClib.h> // From https://github.com/millerlp/RTClib


#define PIN 6

// Tide calculation library setup.
#include "TidelibLaJollaScrippsInstitutionWharfCalifornia.h"
// Other sites available at http://github.com/millerlp/Tide_calculator

// using a 32x8 segment matrix display for the tide chart graph
const int8_t iChartCol=32;
int8_t iChartData[32];
float fChartData[32];
int8_t iChartDataDec[32];
int8_t iMinMax[4];

RTC_DS3231 rtc;

#define BRIGHTNESS 40 // Set BRIGHTNESS to about 1/5 (max = 255)

#define MATRIX_WIDTH  8
#define MATRIX_LENGTH 32
#define LED_COUNT 32*8
// Declare our NeoPixel strip object:
Adafruit_NeoPixel strip(LED_COUNT, PIN, NEO_GRB + NEO_KHZ800);
// Argument 1 = Number of pixels in NeoPixel strip
// Argument 2 = Arduino pin number (most are valid)
// Argument 3 = Pixel type flags, add together as needed:
//   NEO_KHZ800  800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
//   NEO_KHZ400  400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
//   NEO_GRB     Pixels are wired for GRB bitstream (most NeoPixel products)
//   NEO_RGB     Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)
//   NEO_RGBW    Pixels are wired for RGBW bitstream (NeoPixel RGBW products)


void setup() {
  if (! rtc.begin()) {
    Serial.println("Couldn't find RTC");
    Serial.flush();
    while (1) delay(10);
  }
  if (rtc.lostPower()) {
    Serial.println("RTC lost power, let's set the time!");
    // When time needs to be set on a new device, or after a power loss, the
    // following line sets the RTC to the date & time this sketch was compiled
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
    // This line sets the RTC with an explicit date & time, for example to set
    // January 21, 2014 at 3am you would call:
    // rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
  }

  // When time needs to be re-set on a previously configured device, the
  // following line sets the RTC to the date & time this sketch was compiled
  // rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  // This line sets the RTC with an explicit date & time, for example to set
  // January 21, 2014 at 3am you would call:
  // rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));

  strip.begin();           // INITIALIZE NeoPixel strip object (REQUIRED)
  strip.show();            // Turn OFF all pixels ASAP
  strip.setBrightness(BRIGHTNESS);

  // initialize fChartData[]
  for( int8_t i=0; i<iChartCol; i++ ){
     iChartData[i]=0;
     fChartData[i]=0;
     iChartDataDec[i]=0;     
  }
  // For debugging output to serial monitor
  Serial.begin(57600); // Set baud rate to 57600 in serial monitor for slow 8MHz micros

  getChartData();//iChartData, iChartCol);
  getMinMaxData();

  Serial.println(F("Todays tide in 45 minute increments:"));
  for( int8_t x=1; x <= iChartCol; x++ ) {
    Serial.print(F(" Tide height: "));
    //Serial.print(iChartData[x-1],3);
    Serial.print(fChartData[x-1],3);
    //Serial.print(".");Serial.print(iChartDataDec[x-1]);
    Serial.print(F(" ft."));
    Serial.println();
  }
  Serial.println();
}


void loop() {
  // check for new day and run getChartData() on newday( hour=0 )
  static int8_t iLastHour = 24;
  if( rtc.now().hour() == 0 && iLastHour != 0){ // new day
    getChartData();
    getMinMaxData();
    iLastHour=0;
    strip.clear();
  }
  else
    iLastHour = rtc.now().hour();
  const int8_t iSpeed = 10; //lower is faster
  // Fill along the length of the strip in various colors...
  //colorWipe(strip.Color(255,   0,   0)     , iSpeed); // Red
  timeLine(strip.Color(  0,   50, 0)     , iSpeed/5); // light Green
  tideWipe(strip.Color(  0,   0, 75)     , iSpeed); // Blue
  //minMaxPeaks(strip.Color(  0,   0, 255)     , iSpeed/5); // Blue
}

// Fill strip pixels one after another with a color. Strip is NOT cleared
// first; anything there will be covered pixel by pixel. Pass in color
// (as a single 'packed' 32-bit value, which you can get by calling
// strip.Color(red, green, blue) as shown in the loop() function above),
// and a delay time (in milliseconds) between pixels.
void colorWipe(uint32_t color, int wait) {
  for(uint16_t i=0; i<strip.numPixels(); i++) { // For each pixel in strip...
    strip.setPixelColor(i, color);         //  Set pixel's color (in RAM)
    strip.show();                          //  Update strip to match
    delay(wait);                           //  Pause for a moment
  }
}
// Turn on a matrix columns pixel one after another with a color.
// Color(red, green, blue) as shown in the loop() function above),
// and a delay time (in milliseconds) between pixels.
void tideWipe(uint32_t color, int wait) {
  int8_t iCol;
  uint32_t iColor = color;
  for(uint16_t i=0; i<strip.numPixels(); i++) { // For each pixel in strip...
    // check for which column
    iCol = i/MATRIX_WIDTH;
    //Serial.print("column:");Serial.println(iCol);
    // set tide peaks colors
    if( iCol==iMinMax[0] || iCol==iMinMax[1] || iCol==iMinMax[2] || iCol==iMinMax[3] ) 
       if( fChartData[iCol]>=7 || fChartData[iCol]<=-1.0 )
          color=strip.Color(75,0,0); // red marks King Tide
       else 
          color=strip.Color(48,213,200); // turquoise
      //color=strip.Color(48,213,200); // turquoise
    else
      color=iColor;
    if( (iCol % 2) == 0 ) // true == even
        strip.setPixelColor(7+(iCol*MATRIX_WIDTH)-iChartData[iCol], color);         //  Set pixel's color (in RAM)
    else // odd
        strip.setPixelColor((iCol*MATRIX_WIDTH)+iChartData[iCol], color);         //  Set pixel's color (in RAM)
    strip.show();                          //  Update strip to match
    delay(wait);                           //  Pause for a moment
  }
}

void timeLine(uint32_t color, int wait) {
  int8_t iCol, iTimeCol;
  int8_t iHour, iMinutes;
  static int8_t iCurCol=0;

  DateTime curTime;
  curTime = rtc.now();
  iHour = curTime.hour();
  iMinutes = curTime.minute();
  iTimeCol=abs((iHour*60+iMinutes)/45);  
  for(uint16_t i=0; i<strip.numPixels(); i++) { // For each pixel in strip...
    // check for which column
    iCol = i/MATRIX_WIDTH;
    //Serial.print("column:");Serial.println(iCol);
    if( iCol == iTimeCol ) { // true == even
      if( iCol != iCurCol ) // clear previous line
        for(int8_t j=0; j<MATRIX_WIDTH; j++)
          strip.setPixelColor(iCurCol*MATRIX_WIDTH+j, strip.Color(0, 0, 0)); // Clear pixel's
      for(int8_t j=0; j<MATRIX_WIDTH; j++){
        // test if pixel color is already set, skip if isSet
        if( strip.getPixelColor(iCol*MATRIX_WIDTH+j) == 0 ) 
          strip.setPixelColor(iCol*MATRIX_WIDTH+j, color);      //  Set pixel's color (in RAM)
      }
      iCurCol=iCol;
    }
    strip.show();                          //  Update strip to match
    delay(wait);                           //  Pause for a moment
  }
}

void minMaxPeaks(uint32_t color, int wait) {
  int8_t iCol;
  uint32_t iColor, iBrightColor;
  iBrightColor=strip.Color(255,127,127);
  for(uint16_t i=0; i<strip.numPixels(); i++) { // For each pixel in strip...
    // check for which column
    iCol = i/MATRIX_WIDTH;
    //Serial.print("column:");Serial.println(iCol);
    if( iCol==iMinMax[0] || iCol==iMinMax[1] || iCol==iMinMax[2] || iCol==iMinMax[3] ){
      color = iBrightColor;
      if( (iCol % 2) == 0 ) // true == even
          strip.setPixelColor(7+(iCol*MATRIX_WIDTH)-iChartData[iCol], color);         //  Set pixel's color (in RAM)
      else // odd
          strip.setPixelColor((iCol*MATRIX_WIDTH)+iChartData[iCol], color);         //  Set pixel's color (in RAM)
      }
      else
        color = iColor; 
    strip.show();                          //  Update strip to match
    delay(wait);                           //  Pause for a moment
  }
}

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

void getChartData(void) {
  TideCalc myTideCalc; // Create TideCalc object called myTideCalc
  float result;
  DateTime today, startTime;
  //today = DateTime(__DATE__,__TIME__);
  //today = DateTime(2022,3,19,13,29,0);
  today = rtc.now();
  Serial.print(today.year(), DEC);
  Serial.print('/');
  Serial.print(today.month(), DEC);
  Serial.print('/');
  Serial.println(today.day(), DEC);

  startTime = DateTime(today.year(),today.month(),today.day(),00,00,0); // TODO: get today() hr=0 min=0
  // 24 hours in a day but 32 segments...
  //(24hr * 60min * 60sec)/32 // seconds/day / 32 
  for( int8_t x=1; x <= iChartCol; x++ ) {
    result = myTideCalc.currentTide(startTime.unixtime() + ((24 * 60L * 60)/iChartCol) * x);
    fChartData[x-1] = result;
    iChartData[x-1] = (int8_t)round(result);
    // test if low tide is less than 0 and move to zero. Disp only does 0-7'
    if( result < 0.50 )
      iChartData[x-1] = 0;
    int whole = (int)result;
    //Serial.print(result,3);Serial.print("::");Serial.println((int8_t)abs((result-whole)*100));
    iChartDataDec[x-1] = (int8_t)abs((result-whole)*100);
  }
}

void getMinMaxData(void) {
  int8_t iFMin, iFMax, iSMin, iSMax;
  iMinMax[0]=iFMin = findMin(0);
  iMinMax[1]=iFMax = findMax(0);      
  if( iFMax < iFMin ) {
    //iFMax = findMax(iFMin);
    iMinMax[1]= iFMax = findMax(iFMin);      
  }
  // find 2nd Min and Max
  iMinMax[2]=iSMin = findMin(iFMax);
  iMinMax[3]=iSMax = findMax(iSMin);      
  if( iSMax < iSMin ) { // 2nd max returned 0
    //iSMax = findMax(iFMin);
    iMinMax[3]=iSMax = findMax(0);      
  }
  
  Serial.print("First Min=");Serial.println(iFMin);
  Serial.print("First Max=");Serial.println(iFMax);
  Serial.print("Second Min=");Serial.println(iSMin);
  Serial.print("Second Max=");Serial.println(iSMax);
}


int8_t findMin(int iStart)
{
  for( int i=iStart; i<32; i++ )
    if( (i<31) && (fChartData[i]-fChartData[i+1] < 0))
      return i;
  return 0;
}
int8_t findMax(int iStart)
{
  for( int i=iStart; i<32; i++ )
    if( (i<31) && (fChartData[i+1]-fChartData[i] < 0))
      return i;
  return 0;
}
//*************************************

5 Likes

Tide peak detection is a PIA when using just a 24hr window of data. There are a bunch of edge cases to deal with as the tide graph changes and shifts from day to day.

The findMin() and findMax() functions are easy but for the changing tides, treating each day as its own dataset doesn’t seem to be the right technique.

For example yesterday there were only 3 peaks and then today where the first Max peak happens on the 2nd data point while the first Min peak ended up on the first data point since it was rising.

I can’t use a circular buffer since the tide graph is really continuous so the end of the day has nothing to do with the beginning of the day.

Currently, I’m looking for first Min peak and first Max peak and working the edge cases from there. I even added one extra data point at the end of the day to help with one edge case.

Data:

First Min=11
First Max=1
Second Min=24
Second Max=20
Todays tide in 45 minute increments:
 Tide height: 5.040 ft.
 Tide height: 5.060 ft.
 Tide height: 4.844 ft.
 Tide height: 4.408 ft.
 Tide height: 3.794 ft.
 Tide height: 3.064 ft.
 Tide height: 2.292 ft.
 Tide height: 1.557 ft.
 Tide height: 0.928 ft.
 Tide height: 0.463 ft.
 Tide height: 0.195 ft.
 Tide height: 0.136 ft.
 Tide height: 0.269 ft.
 Tide height: 0.558 ft.
 Tide height: 0.950 ft.
 Tide height: 1.385 ft.
 Tide height: 1.800 ft.
 Tide height: 2.147 ft.
 Tide height: 2.390 ft.
 Tide height: 2.515 ft.
 Tide height: 2.529 ft.
 Tide height: 2.458 ft.
 Tide height: 2.341 ft.
 Tide height: 2.227 ft.
 Tide height: 2.161 ft.
 Tide height: 2.182 ft.
 Tide height: 2.313 ft.
 Tide height: 2.560 ft.
 Tide height: 2.911 ft.
 Tide height: 3.334 ft.
 Tide height: 3.785 ft.
 Tide height: 4.213 ft.
 Extra: 4.56

Source code:
Note: findMin() and findMax() take the index in the array to start searching.

void getMinMaxData(void) {
  int8_t iFMin, iFMax, iSMin, iSMax;
  bool bStart = false; //true=hightide, false=lowtide
  iFMin = findMin(0);
  iFMax = findMax(0);      
  if( iFMin > 8 || (iFMax-iFMin<5) ) {  // there should be a max BEFORE the first min
      bStart = true;
      iFMax = findMax(0);      
      iFMin = findMin(iFMax);
  }
  else {
      bStart = false;
      iFMin = findMin(0);     
      iFMax = findMax(iFMin);
  }
  /*if( iFMax < iFMin ) {
    //iFMax = findMax(iFMin);
    iMinMax[1]= iFMax = findMax(iFMin);      
  }*/
  // find 2nd Min and Max
  if( bStart ) { // 1max, 1min, 2max, 2min
      iSMax = findMax(iFMin);
      iSMin = findMin(iSMax);      
  }
  else { // 1min, 1max, 2min, 2max
      iSMin = findMin(iFMax);
      iSMax = findMax(iSMin);      
  }

  // deal with edge cases like the high or low tide ending on the 32 element
  if( !bStart && (iSMax < iSMin) && (iFMin==8)) { // 2nd max returned 0
    iSMax = 31; // set to last hour      
  }

  // populate the MinMax[] list
  if( bStart ) { // 1max, 1min, 2max, 2min
      iMinMax[0]=iFMax;
      iMinMax[1]=iFMin;
      iMinMax[2]=iSMax;
      iMinMax[3]=iSMin;
  }
  else { // 1min, 1max, 2min, 2max
      iMinMax[0]=iFMin;
      iMinMax[1]=iFMax;
      iMinMax[2]=iSMin;
      iMinMax[3]=iSMax;
  }
  Serial.print("First Min=");Serial.println(iFMin);
  Serial.print("First Max=");Serial.println(iFMax);
  Serial.print("Second Min=");Serial.println(iSMin);
  Serial.print("Second Max=");Serial.println(iSMax);
}
2 Likes

I have an idea… I will have the previous peaks in the iMinMax[4] array when the next call to findPeaks() is made so I could analyze it to find out if the last peak was a Max or Min and then I’d know the sequence(min/max/min/max or max/min/max/min) to start with for the next/new day. Maybe even use the previous array location in time to test to make sure the first new peak’s starting time makes sense.

On bootup, when iMinMax is all zeros I could try modify the current getMinMaxData() function to get the previous day’s data and generate the previous peaks data then use the updated getMinMaxData() using the previous peaks information…

2 Likes