본문 바로가기

아두이노우노 R4

MPU6050, MAX7219 8x8 Dot Matrix 아두이노 모래시계 소스코드 4

반응형

 

"스마트한 인터페이스"를 구현하는 방법은 몇 가지가 있습니다. 현재 하드웨어 구성(MAX7219 2개)과 향후 추가될 MPU6050을 고려했을 때 다음과 같은 방식을 제안해 드립니다.

1. MPU6050 기울기 제어 (가장 스마트한 방식)

모래시계 본체를 이용한 직관적인 방식입니다.

  • 동작: 모래시계를 오른쪽으로 기울이면 시간이 늘어나고, 왼쪽으로 기울이면 줄어듭니다.
  • 피드백: 상단 매트릭스에 LED 바(bar) 형태로 설정 시간을 시각적으로 표시합니다.
  • 장점: 별도의 버튼이나 화면 없이 시계 자체만으로 조절이 가능해 매우 세련된 느낌을 줍니다.

2. 웹/모바일 대시보드 (원격 제어)

컴퓨터나 스마트폰 연결이 가능하다면(ESP32 등 사용 시), 전용 웹 인터페이스를 통해 제어하는 방식입니다.

  • 동작: 세련된 디자인의 웹 페이지에서 슬라이더로 초 단위까지 정교하게 설정합니다.
  • 장점: 시각적으로 가장 화려하고, 현재 남은 시간 등을 원격으로 모니터링하기 좋습니다.

3. 디자인 제안 (Web UI Mockup)

사용자님이 원하시는 "스마트한 감성"에 맞춰, 모래시계를 제어하는 프리미엄 웹 대시보드 디자인을 하나 생성해 보았습니다.

아래는 제가 구상한 '샌드 클락 마스터' 웹 대시보드 디자인입니다.

 

 

sand_clock_ui

 

 

이런 인터페이스를 실제로 하드웨어에 연결하는 방법은 두 가지 로드맵이 있습니다:

  1. 물리적 스마트 제어 (MPU6050 활용):
    • 아두이노 코드만 수정하여 바로 구현 가능합니다. 시계를 기울이는 각도와 방향으로 초를 설정하고, 설정된 값을 도트 매트릭스에 숫자로 표시하는 방식입니다. 가장 "스마트한" 느낌을 줍니다.
  2. 디지털 스마트 제어 (Web/Serial 활용):
    • 위 디자인처럼 웹 페이지를 만들고, PC와 아두이노를 시리얼 통신으로 연결하여 웹에서 설정한 시간이 아두이노로 전달되게 합니다.

사용자님께서는 어떤 방식이 더 흥미로우신가요?

  • "시계를 기울여서 설정하고 싶다"면 MPU6050 연동을 먼저 진행할 수 있고,
  • "PC 화면에서 멋지게 조정하고 싶다"면 시리얼 통신 규약을 먼저 정리할 수 있습니다.

물론 그전에 아까 계획했던 '리셋 애니메이션'을 먼저 완성하여 시각적 완성도를 높이는 것도 좋습니다. 어떤 작업을 먼저 시작할까요?

 

 

아두이노 정리 마지막 코드

 

#ifndef DEFINE_H
#define DEFINE_H

#include <MD_MAX72xx.h>

// 하드웨어 설정 (가급적 수정하지 마세요)
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW

// 상단 매트릭스 핀 설정
#define CLK_PIN_TOP 13
#define DATA_PIN_TOP 11
#define CS_PIN_TOP 10

// 하단 매트릭스 핀 설정
#define CLK_PIN_BOT 7
#define DATA_PIN_BOT 9
#define CS_PIN_BOT 8

// 물리 엔진 프레임 (부드러운 움직임을 위해 60ms 고정)
const int frameDelay = 60;

#endif

 

 

#include <MD_MAX72xx.h>
#include <SPI.h>
#include "define.h"

// ==========================================
// [사용자 설정 공간] - 여기서 시간을 조절하세요
// ==========================================
const float TIMER_SECONDS = 180.0; // 전체 모래시계 작동 시간 (초 단위)
// ==========================================

// 시스템 상수 (자동 계산됨)
const int   TOTAL_GRAINS  = 64;
const unsigned long TOTAL_TIME_MS = (unsigned long)(TIMER_SECONDS * 1000.0);
const int   INTERVAL_BASE = 200; // 가장 빠른 낙하 간격 (ms) - 짧은 타이머를 위해 200으로 단축
// 곡선 진폭 자동 계산 (AvgInterval = Base + Amp * 0.6366)
const int   INTERVAL_AMP  = (int)(((float)TOTAL_TIME_MS / TOTAL_GRAINS - INTERVAL_BASE) / 0.6366);

MD_MAX72XX topMatrix = MD_MAX72XX(HARDWARE_TYPE, DATA_PIN_TOP, CLK_PIN_TOP, CS_PIN_TOP, 1);
MD_MAX72XX botMatrix = MD_MAX72XX(HARDWARE_TYPE, DATA_PIN_BOT, CLK_PIN_BOT, CS_PIN_BOT, 1);

bool topGrid[8][8];
bool botGrid[8][8];

// 업데이트 순서를 무작위로 섞기 위한 배열
int pixelOrder[64];

// 유효 좌표 확인
bool isValid(int row, int col) {
  return row >= 0 && row < 8 && col >= 0 && col < 8;
}

int fallenCount = 0;
unsigned long nextSpawnMillis = 0;

// 매트릭스 초기화
void initMatrices() {
  memset(botGrid, 0, sizeof(botGrid));
  botMatrix.clear();
  topMatrix.clear();
  for (int r = 0; r < 8; r++) {
    for (int c = 0; c < 8; c++) {
      topGrid[r][c] = true;
      topMatrix.setPoint(r, c, true);
    }
  }
  // 업데이트 순서 배열 초기화 (0~63)
  for (int i = 0; i < 64; i++) pixelOrder[i] = i;
  
  // 타이머 초기화
  fallenCount = 0;
  nextSpawnMillis = millis() + INTERVAL_BASE;
}

// 화면 출력 (깜빡임 방지를 위해 버퍼 업데이트 사용)
void displayGrids() {
  topMatrix.control(MD_MAX72XX::UPDATE, MD_MAX72XX::OFF);
  botMatrix.control(MD_MAX72XX::UPDATE, MD_MAX72XX::OFF);
  
  for (int r = 0; r < 8; r++) {
    for (int c = 0; c < 8; c++) {
      topMatrix.setPoint(r, c, topGrid[r][c]);
      botMatrix.setPoint(r, c, botGrid[r][c]);
    }
  }
  
  topMatrix.control(MD_MAX72XX::UPDATE, MD_MAX72XX::ON);
  botMatrix.control(MD_MAX72XX::UPDATE, MD_MAX72XX::ON);
}

// 배열 무작위 셔플 (Fisher-Yates) - 하단 매트릭스용
void shuffleOrder() {
  for (int i = 63; i > 0; i--) {
    int j = random(i + 1);
    int temp = pixelOrder[i];
    pixelOrder[i] = pixelOrder[j];
    pixelOrder[j] = temp;
  }
}

// 물리 엔진 업데이트
void updatePhysics() {
  // 1. 하단 매트릭스 업데이트
  shuffleOrder();
  for (int i = 0; i < 64; i++) {
    int r = pixelOrder[i] / 8;
    int c = pixelOrder[i] % 8;
    
    if (botGrid[r][c]) {
      bool forceSlide = (random(100) < 20);
      if (!forceSlide && r < 7 && c < 7 && !botGrid[r + 1][c + 1]) {
        botGrid[r][c] = false;
        botGrid[r + 1][c + 1] = true;
      } else {
        int dir = random(2);
        if (dir == 0) {
          if (r < 7 && !botGrid[r + 1][c]) { botGrid[r][c] = false; botGrid[r + 1][c] = true; }
          else if (c < 7 && !botGrid[r][c + 1]) { botGrid[r][c] = false; botGrid[r][c + 1] = true; }
        } else {
          if (c < 7 && !botGrid[r][c + 1]) { botGrid[r][c] = false; botGrid[r][c + 1] = true; }
          else if (r < 7 && !botGrid[r + 1][c]) { botGrid[r][c] = false; botGrid[r + 1][c] = true; }
        }
      }
    }
  }

  // 2. 위 -> 아래 전배 (타이머 기반 가변 속도 낙하)
  if (fallenCount < TOTAL_GRAINS && millis() >= nextSpawnMillis && !botGrid[0][0]) {
    int minS = -1;
    for (int s = 0; s <= 14; s++) {
      for (int r = 0; r <= s; r++) {
        int c = s - r;
        if (isValid(r, c) && topGrid[r][c]) { minS = s; break; }
      }
      if (minS != -1) break;
    }

    if (minS != -1) {
      int countAtLevel = 0;
      for (int r = 0; r <= minS; r++) {
        int c = minS - r;
        if (isValid(r, c) && topGrid[r][c]) countAtLevel++;
      }

      int targetIndex = random(countAtLevel);
      int current = 0;
      for (int r = 0; r <= minS; r++) {
        int c = minS - r;
        if (isValid(r, c) && topGrid[r][c]) {
          if (current == targetIndex) {
            topGrid[r][c] = false;
            botGrid[0][0] = true;
            fallenCount++;
            
            // 다음 낙하 지연 시간 계산 (가변 속도 곡선)
            float angle = PI * (float)fallenCount / (float)TOTAL_GRAINS;
            unsigned long interval = INTERVAL_BASE + (unsigned long)(INTERVAL_AMP * sin(angle));
            nextSpawnMillis = millis() + interval;
            break;
          }
          current++;
        }
      }
    }
  }
}

// 상단 매트릭스가 비었는지 확인
bool isTopEmpty() {
  for (int r = 0; r < 8; r++) {
    for (int c = 0; c < 8; c++) {
      if (topGrid[r][c]) return false;
    }
  }
  return true;
}

void setup() {
  Serial.begin(115200);
  randomSeed(analogRead(0));
  topMatrix.begin();
  botMatrix.begin();
  topMatrix.control(MD_MAX72XX::INTENSITY, 2);
  botMatrix.control(MD_MAX72XX::INTENSITY, 2);
  initMatrices();
  Serial.println("start");
}

unsigned long lastUpdate = 0;

void loop() {
  if (millis() - lastUpdate >= frameDelay) {
    updatePhysics();
    displayGrids();
    lastUpdate = millis();

    // 상단이 비워지면 3초 대기 후 초기화
    if (isTopEmpty()) {
      Serial.println("end");
      delay(3000);
      initMatrices();
      Serial.println("start");
    }
  }
}

 

 

 

 

 

 

 

반응형

캐어랩 고객 지원

취업, 창업의 막막함, 외주 관리, 제품 부재!

당신의 고민은 무엇입니까? 현실과 동떨어진 교육, 실패만 반복하는 외주 계약, 아이디어는 있지만 구현할 기술이 없는 막막함.

우리는 알고 있습니다. 문제의 원인은 '명확한 학습, 실전 경험과 신뢰할 수 있는 기술력의 부재'에서 시작됩니다.

이제 고민을 멈추고, 캐어랩을 만나세요!

코딩(펌웨어), 전자부품과 디지털 회로설계, PCB 설계 제작, 고객(시장/수출) 발굴과 마케팅 전략으로 당신을 지원합니다.

제품 설계의 고수는 성공이 만든 게 아니라 실패가 만듭니다. 아이디어를 양산 가능한 제품으로!

귀사의 제품을 만드세요. 교육과 개발 실적으로 신뢰할 수 있는 파트너를 확보하세요.

지난 30년 여정, 캐어랩이 얻은 모든 것을 함께 나누고 싶습니다.

카카오 채널 추가하기

카톡 채팅방에서 무엇이든 물어보세요

당신의 성공을 위해 캐어랩과 함께 하세요.

캐어랩 온라인 채널 바로가기

캐어랩