Digital Hourglass

Hey FOLKS,
we are currently trying to create an digital hourglass using our ESP32, and for the led we are using MAX7219 (8x8 LED)

We got Problems with our current code because it sets the LED s in the wrong direction, better said it only sets the LEDs on TRUE without the animation of the falling led.

Here is our code:
#include <MD_MAX72xx.h>
#include <SPI.h>

#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define MAX_DEVICES 1
#define DIN_PIN 23
#define CS_PIN 5
#define CLK_PIN 18

int mapMatrix1[8][8];
int mapMatrix2[8][8];
bool hilfsmatrix[8][8];

MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);

void setup() {
mx.begin();
mx.clear();
}

void loop() {

int max = 7;
int diagonal1ist3 = 0;

for (int i = 0; i <= max; ++i) {

mx.setPoint(i, i, true);
delay(300);

if((mx.getPoint(max-diagonal1ist3, max-diagonal1ist3) && (mx.getPoint(max-diagonal1ist3-1, max-diagonal1ist3)) && (mx.getPoint(max-diagonal1ist3-1, max-diagonal1ist3-1)))){
  diagonal1ist3++;
}


// Abfrage für links
if((mx.getPoint(max-diagonal1ist3,max-diagonal1ist3-1) && (mx.getPoint(max-diagonal1ist3-1,max-diagonal1ist3-1)))){
  for(int abfragelinks = 0; abfragelinks<=diagonal1ist3+1; abfragelinks++){

      mx.setPoint(max-diagonal1ist3-1,max-abfragelinks, true);
      delay(100);
  }
}


// Abfrage für rechts
if((mx.getPoint(max-diagonal1ist3,max-diagonal1ist3) && (mx.getPoint(max-diagonal1ist3-1,max-diagonal1ist3-1)))){
  for(int abfragerechts = 0; abfragerechts<=diagonal1ist3+1; abfragerechts++){

      mx.setPoint(max-abfragerechts, max-diagonal1ist3-1, true);
      delay(100);
  }   
}

if(i<7-diagonal1ist3){
  mx.setPoint(i, i, false);
}

}
}

if u got any ideas or some help that would be appreciated a lot!!

Welcome to the forum

Please follow the advice given in the link below when posting code, in particular the section entitled 'Posting code and common code problems'

Use code tags (the < CODE/ > icon above the compose window) to make it easier to read and copy for examination

https://forum.arduino.cc/t/how-to-get-the-best-out-of-this-forum

1 Like

Hi,

you mean something like that?

(In german, but with an image attached)

I can give you the code if you like. But it does not look very realistic :sweat_smile:

Hey,
Yes this is exactly what we are trying to do.
We got some example code but they did use other components and the code somehow does not work then so we tryed to do it ourselves.
But as I said we got problems with the led going down the right path and stuff.

But if u could sent usbur could this might help so we can check how u did solve this.
Thanks,
Jonas

What problems are you finding? Would you show your code?

Here is how I would write a formula...

(line 1 has 1 LED)

  • LED #1 location in each square is 0,0

(line 2 has 2 LEDs)

  • LED 2 = 1, 0
  • LED 3 = 0, 1

(line 3 has 3 LEDs)

  • LED 4 = 2, 0
  • LED 5 = 1,1
  • LED 6 = 0, 2

(line 4)

  • 7 = 3, 0
  • 8 = 2, 1
  • 9 = 1, 2
  • 10 = 0, 3

See the pattern?

  • Next line number (L) increases by 1 (toward a MAX of 7)
  • First LED in line number is (L-1, 0)
  • Next LEDs - "x" decreases toward 0, "y" increases to L-1
  • Last LED in line number is (0, L-1)
  • After you get midway (7,0) (1, 6) (2, 5) (3, 4) (4, 3) (5, 2) ( (6, 1) (0,7)... decrease the number of "x,y" pairs
  • (line 14 has 2 LEDs (7, 6)(6, 7)
  • (line 15 has 1 LED (7,7)

Each time you set "x, y" to OFF on the top square, you set "x, y" ON on the bottom square.

Give it a try...

1 Like

Additionally to the great explaination above, it helped me to draw the matrix in Excel:
grafik

And here is my (not cleaned up) code:

#define PIN_DIN 12
#define PIN_CLK 11
#define PIN_CS  10
#define NUM_DEVICES 2

#include <LedControl.h>

LedControl lc = LedControl(PIN_DIN, PIN_CLK, PIN_CS, NUM_DEVICES);

#define Matrix_Width 8
#define Matrix_Heigth 8
#define PIN_Tilt 5

#define LedMatrixAttached 1
#define Speedup 0

bool top[Matrix_Width][Matrix_Heigth];
bool bottom[Matrix_Width][Matrix_Heigth];

enum Orientations
{
  TopBottom = 0,
  BottomTop = 1
} orientation = TopBottom;

typedef enum {
  Top = 0,
  Bottom = 1
} Glass;

const int maxIterations = 513;
int counter = 0;
int blinkCounter = 0;
unsigned long startTime = 0;
unsigned long lastDebounceTime = 0;
unsigned long debounceDelay = 50;

void setup() {
  Serial.begin(115200);  
  pinMode(PIN_Tilt, INPUT_PULLUP);
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, HIGH);

  randomSeed(analogRead(0));

  SetupMatrix();
}

void loop() {  
  PhysicLoop();  
}

void SetupMatrix() {
  for (int index = 0; index < lc.getDeviceCount(); index++) {
    lc.shutdown(index, false);
    lc.setIntensity(index, 2);
    lc.clearDisplay(index);
  }
  orientation = (digitalRead(PIN_Tilt) > 0) ? BottomTop : TopBottom;
  CheckTilt();
  ResetMatrix();
}

void ResetMatrix() {  
  SetAll(GetGlass(Top), orientation == TopBottom);
  SetAll(GetGlass(Bottom), orientation == BottomTop);
  counter = 0;
  startTime = 0;
  blinkCounter = 0;
}

void PhysicLoop() {
  CheckTilt();
  
  if (startTime == 0)
    startTime = millis();
  if (counter >= maxIterations + 1) {
    EndBlink();
    return;
  } else
    counter++;

  bool topTick = IterateRows(Top);
  bool bottomTick = IterateRows(Bottom);
  bool transferGrain;
  if (!topTick && !bottomTick)
    transferGrain = TransferPixel();
  if (!topTick && !bottomTick && !transferGrain) {
    EndClock();
    return;
  }

#if !LedMatrixAttached
  Print();
#endif

  if (CheckGlasses())
    counter = 1000;

#if Speedup
  delay(100);
  return;
#endif

  int iterations = 513;
  unsigned long duration = 60000 * 15;
  const int runtime = 1538;
  delay((duration - runtime) / iterations);
}

bool TransferPixel() {
  bool topValue = GetValue(Top, Matrix_Width + Matrix_Heigth - 2, 0);
  bool bottomValue = GetValue(Bottom, 0, 0);

  if (topValue && !bottomValue)
    CopyPixel(Top, Matrix_Width + Matrix_Heigth - 2, 0, Bottom, 0, 0);
  return topValue && !bottomValue;
}

bool IterateRows(uint8_t glass) {
  bool prevLineHasValues = false;
  for (uint8_t i = 0; i < Matrix_Heigth + Matrix_Width - 1; i++) {
    bool lineFilled = IsLineFilled(glass, i);
    bool lineHasGaps = lineFilled ? HasLineGap(glass, i) : true;
    bool nextLineHasGaps = i < Matrix_Heigth + Matrix_Width - 1 ? HasLineGap(glass, i + 1) : false;
    bool ticked = false;

    if (prevLineHasValues && lineHasGaps)
      ticked = FillGapFromAbove(glass, i);
    if (lineFilled && lineHasGaps && nextLineHasGaps)
      ticked = FillGapBelow(glass, i);
    //only one tick per iteration
    if (ticked)
      return true;
    prevLineHasValues = lineFilled;
  }
  return false;
}

bool FillGapFromAbove(uint8_t glass, uint8_t row) {

  uint8_t columns[Matrix_Width];
  uint8_t NoOfColums = GetRow(row - 1, columns);
  uint8_t colPrev;

  NoOfColums = GetRow(row, columns);
  uint8_t colCurr;
  for (colCurr = 0; colCurr < NoOfColums; colCurr++) {
    bool value = GetValue(glass, row, colCurr);
    if (!value)
      break;
  }

  colPrev = GetNextPixel(glass, row - 1, row, colCurr, true);

  CopyPixel(glass, row - 1, colPrev, glass, row, colCurr);
  return true;
}

bool FillGapBelow(uint8_t glass, uint8_t row) {

  uint8_t columns[Matrix_Width];
  uint8_t NoOfColums = GetRow(row + 1, columns);
  uint8_t colNext;
  uint8_t colCurr;

  NoOfColums = GetRow(row, columns);
  for (colCurr = 0; colCurr < NoOfColums; colCurr++)
    if (GetValue(glass, row, colCurr))
      break;

  colNext = GetNextPixel(glass, row + 1, row, colCurr, false);

  //Line below already full:
  if (GetValue(glass, row + 1, colNext))
    return false;
  CopyPixel(glass, row, colCurr, glass, row + 1, colNext);
  return true;
}

bool IsLineFilled(uint8_t glass, uint8_t row) {
  uint8_t columns[Matrix_Width];
  uint8_t NoOfColums = GetRow(row, columns);
  for (uint8_t i = 0; i < NoOfColums; i++) {
    bool value = GetValue(glass, row, i);
    if (value)
      return true;
  }
  return false;
}

bool HasLineGap(uint8_t glass, uint8_t row) {
  uint8_t columns[Matrix_Width];
  uint8_t NoOfColums = GetRow(row, columns);
  for (uint8_t i = 0; i < NoOfColums; i++) {
    bool value = GetValue(glass, row, i);
    if (!value)
      return true;
  }
  return false;
}

uint8_t GetGlass(uint8_t glass) {
  if (orientation == TopBottom)
    return glass == Top ? 0 : 1;
  return glass == Top ? 1 : 0;
}

void Print() {
  PrintMatrix(Top);
  PrintMatrix(Bottom);
}

void PrintMatrix(uint8_t glass) {
  uint8_t no = GetGlass(glass);
  switch (no) {
    case 0:
      PrintArray(top);
      break;
    case 1:
      PrintArray(bottom);
      break;
  }
}

void PrintArray(bool arr[Matrix_Heigth][Matrix_Width]) {
  for (int i = 0; i < Matrix_Heigth + Matrix_Width - 1; i++) {
    int startRow = max(0, i - Matrix_Width + 1);
    int startCol = min(i, Matrix_Width - 1);
    int numElements = min(i + 1, Matrix_Heigth - startRow);
    for (uint8_t space = 0; space < Matrix_Width - numElements; space++)
      Serial.print(" ");
    for (int j = 0; j < numElements; j++) {
      int x = startRow + j;
      int y = startCol - j;
      int value = arr[x][y];
      Serial.print(value ? "X" : "0");
      Serial.print(" ");
    }
    Serial.println();
  }
}

void SetAll(uint8_t glass, bool state) {
  for (int y = 0; y < Matrix_Heigth; y++)
    for (int x = 0; x < Matrix_Width; x++)
      SetPixelByCoordinates(glass, x, y, state);
}

void CopyPixel(uint8_t glassFrom, uint8_t rowFrom, uint8_t colFrom, uint8_t glassTo, uint8_t rowTo, uint8_t colTo) {
  bool state = GetValue(glassFrom, rowFrom, colFrom);
  SetPixel(glassFrom, rowFrom, colFrom, !state);
  SetPixel(glassTo, rowTo, colTo, state);
}

void CopyPixelByCoordinates(uint8_t glassFrom, uint8_t xFrom, uint8_t yFrom, uint8_t glassTo, uint8_t xTo, uint8_t yTo) {
  SetPixelByCoordinates(glassFrom, xFrom, yFrom, false);
  SetPixelByCoordinates(glassTo, xTo, yTo, true);
}

void SetPixel(uint8_t glass, uint8_t row, uint8_t col, bool state) {
  uint8_t x;
  uint8_t y;
  GetCoordinates(row, col, x, y);
  SetPixelByCoordinates(glass, x, y, state);
}
void SetPixelByCoordinates(uint8_t glass, uint8_t x, uint8_t y, bool state) {
  uint8_t glassId = GetGlass(glass);
  
  switch (glassId) {
    case 0:
      top[x][y] = state;
      break;
    case 1:
      bottom[x][y] = state;
      break;
  }
  //Invert matrixes...
  lc.setLed(glassId ? 0 : 1, x, y, state);
}

void GetCoordinates(uint8_t row, uint8_t col, uint8_t& x, uint8_t& y) {
  if (orientation == BottomTop)
    row = (Matrix_Width + Matrix_Heigth - 2) - row;

  int startRow = max(0, row - Matrix_Width + 1);
  int startCol = min(row, Matrix_Width - 1);
  int numElements = min(row + 1, Matrix_Heigth - startRow);
  x = startRow + col;
  y = startCol - col;
}

void PrintPixel(uint8_t glass, uint8_t row, uint8_t col) {
  uint8_t x;
  uint8_t y;
  GetCoordinates(row, col, x, y);
  bool value;
  switch (GetGlass(glass)) {
    case 0:
      value = top[x][y];
      break;
    case 1:
      value = bottom[x][y];
      break;
  }
  Serial.print(value ? "X" : "0");
  Serial.print(" (" + String(row));
  Serial.print("/" + String(col));
  Serial.print(") [" + String(x));
  Serial.print("," + String(y));
  Serial.print("]");
  Serial.print(" " + String(GetLedNo(x, y)));
  Serial.print(" ");
}

uint8_t GetLedNo(uint8_t x, uint8_t y) {
  return x + y * Matrix_Width;
}

uint8_t GetRow(uint8_t rowNo, uint8_t *columns) {
  if (orientation == BottomTop)
    rowNo = (Matrix_Width + Matrix_Heigth - 2) - rowNo;

  uint8_t c = 0;
  int startRow = max(0, rowNo - Matrix_Width + 1);
  int startCol = min(rowNo, Matrix_Width - 1);
  int numElements = min(rowNo + 1, Matrix_Heigth - startRow);
  for (int j = 0; j < numElements; j++) {
    int row = startRow + j;
    int col = startCol - j;
    columns[c++] = col;
  }
  if (c <= Matrix_Width - 1)
    columns[c] = 255;
  return numElements;
}
uint8_t GetNextPixel(uint8_t glass, uint8_t rowTo, uint8_t rowFrom, uint8_t colFrom, bool state) {
  uint8_t colTo = 0;
  uint8_t columns[Matrix_Width];
  uint8_t NoOfPrevColumns = GetRow(rowFrom, columns);
  uint8_t NoOfColumns = GetRow(rowTo, columns);

  if (NoOfColumns <= 1)
    return colTo;

  //X 0
  // 0
  if (NoOfColumns < NoOfPrevColumns) {
    if (colFrom == 0)
      colTo = 0;
    else if (colFrom >= NoOfColumns - 1)
      colTo = NoOfColumns - 1;
    else
      colTo = random(colFrom - 1, colFrom + 1);
  }

  // 0 0
  //X 0 0
  if (NoOfColumns > NoOfPrevColumns) {
    colTo = random(colFrom, colFrom + 2);
  }

  if (GetValue(glass, rowTo, colTo) == state)
    return colTo;

  if (colFrom > NoOfColumns - 1)
    colFrom = NoOfColumns - 1;
  if (random(0, 2) == 1) {
    //Next
    for (colTo = colFrom; colTo < NoOfColumns; colTo++)
      if (GetValue(glass, rowTo, colTo) == state)
        return colTo;
  } else {
    //Prev
    for (colTo = colFrom; colTo > 0; colTo--)
      if (GetValue(glass, rowTo, colTo) == state)
        return colTo;
  }

  //Fallback:
  for (colTo = 0; colTo < NoOfColumns; colTo++)
    if (GetValue(glass, rowTo, colTo) == state)
      return colTo;

  return colTo;
}
bool GetValue(uint8_t glass, uint8_t row, uint8_t col) {
  uint8_t x;
  uint8_t y;
  GetCoordinates(row, col, x, y);
  return GetValueByCoordinates(glass, x, y);
}
bool GetValueByCoordinates(uint8_t glass, uint8_t x, uint8_t y) {
  switch (GetGlass(glass)) {
    case 0:
      return top[x][y];
      break;
    case 1:
      return bottom[x][y];
      break;
  }
}

void CheckTilt() {
  int tiltValue = digitalRead(PIN_Tilt);
  if((orientation == TopBottom ? LOW : HIGH) == tiltValue){
    lastDebounceTime = millis();
    return;
  }
  if ((millis() - lastDebounceTime) > debounceDelay)
    orientation = tiltValue;
  else
    return;
  
  digitalWrite(LED_BUILTIN, orientation == TopBottom ? LOW : HIGH);
  Serial.println(orientation == TopBottom ? "Top Bottom" : "Bottom Top");
  //change counter:
  if(counter > 0 && counter < maxIterations)
    counter = maxIterations - counter;
  if (counter >= 1000)
    SetupMatrix();
}

bool CheckGlasses() {
  uint8_t topCounter = 0;
  uint8_t bottomCounter = 0;
  for (uint8_t glass = 0; glass <= 1; glass++)
    for (int y = 0; y < Matrix_Heigth; y++)
      for (int x = 0; x < Matrix_Width; x++)
        if (GetValueByCoordinates(glass, x, y))
          glass == 0 ? topCounter++ : bottomCounter++;
  if (topCounter + bottomCounter != Matrix_Heigth * Matrix_Width) {
    Serial.println("Failed!");
    Serial.println("Top: " + String(topCounter));
    Serial.println("Bottom: " + String(bottomCounter));
    Serial.println("Counter: " + String(counter));
    return true;
  }
  return false;
}
void EndClock() {
  //ToDo: Blink or something...
  Serial.println("End iterations: " + String(counter));
  Serial.print("Millis: ");
  Serial.println(millis() - startTime);
  counter = 1000;
}
void EndBlink() {
#if LedMatrixAttached
  if(blinkCounter == 50){
    SetAll(GetGlass(Top), false);
    SetAll(GetGlass(Bottom), false);
    blinkCounter++;
    return;
  }
  if(blinkCounter > 50)
    return;
  SetAll(Top, true);
  SetAll(Bottom, true);
  for(int brightness = 0; brightness <= 5; brightness++){    
    lc.setIntensity(Top, brightness);    
    lc.setIntensity(Bottom, 5 - brightness);
    delay(50);    
  }
  for(int brightness = 0; brightness <= 5; brightness++){
    lc.setIntensity(Bottom, brightness);    
    lc.setIntensity(Top, 5 - brightness);
    delay(50);    
  }  
  blinkCounter++;
#endif
}
2 Likes

I started a wokwi... [edit] (but NeoPixel, not MAX7219)

Thanks we will check this out aswell!

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.