arduino로 만든 모래 시계 프로젝트는 여기까지 한다.
사람이 하는 일의 대부분은 다시 하는 일이다. 다시 만들고, 다시 쓰고, 다시 그리고, 다시 말하는 일이다. 다시 할 때는 이전과 다르다. 엔트로피는 증가하고, 돌이키기 힘들고, 편리와 효율성이라고 생각한 것들을 구현한다.
아두이노 우노보드에서 만든 모래시계 프로토 타입은 여기까지 한다. 왜? esp32로 할 거니까 말이다. 헤더파일과 아주 잘 동작하는 소스 코드를 여기에 보관한다.
define.h
#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
아두이노 프로그램 mpu-max-sandclock.ino
#include "define.h"
#include <MD_MAX72xx.h>
#include <SPI.h>
// ==========================================
// [사용자 설정 공간] - 여기서 시간을 조절하세요
// ==========================================
const float TIMER_SECONDS = 30.0; // 전체 모래시계 작동 시간 (초 단위)
// ==========================================
// 시스템 상수 (자동 계산됨)
const int TOTAL_GRAINS = 64;
const unsigned long TOTAL_TIME_MS = (unsigned long)(TIMER_SECONDS * 1000.0);
const unsigned long INTERVAL_MS =
TOTAL_TIME_MS / TOTAL_GRAINS; // 고정 낙하 간격
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_MS;
}
// 화면 출력 (깜빡임 방지를 위해 버퍼 업데이트 사용)
void displayGrids() {
static uint32_t animFrame = 0;
animFrame++;
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]);
}
}
// 하단 매트릭스 더미 높이 계산
int pileTopS = 14;
for (int r = 0; r < 8; r++) {
for (int c = 0; c < 8; c++) {
if (botGrid[r][c] && (r + c < pileTopS))
pileTopS = r + c;
}
}
// 하단 매트릭스 출력 (줄줄 흐르는 '실' 효과)
botMatrix.clear();
for (int r = 0; r < 8; r++) {
for (int c = 0; c < 8; c++) {
// 1. 실제 쌓여있는 모래 출력
if (botGrid[r][c]) {
botMatrix.setPoint(r, c, true);
}
// 2. 낙하 경로(r == c)에 흐르는 줄기 효과 (잔상 애니메이션)
// 입구(0,0)부터 더미 최상단(pileTopS)까지 선을 그림
if (r == c && (r + c < pileTopS)) {
// 움직이는 파동 패턴: 3칸 중 2칸을 켜서 선처럼 보이게 함
if ((r + c + animFrame / 2) % 3 != 0) {
botMatrix.setPoint(r, c, true);
}
}
}
}
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. 하단 매트릭스 더미 높이(입구로부터의 최단 거리) 계산
int pileTopS = 14;
for (int r = 0; r < 8; r++) {
for (int c = 0; c < 8; c++) {
if (botGrid[r][c] && (r + c < pileTopS))
pileTopS = r + c;
}
}
// 2. 다중 낙하 스트림을 위한 프레임 제어
// 한 스폰 간격(INTERVAL_MS) 동안 공중의 모래알들이 K번 이동하게 하여 스트림
// 형성
const int K = 2; // K가 작을수록 낙하 속도가 느려지고 공중의 알 수가 많아짐
unsigned long subInterval = INTERVAL_MS / K;
static unsigned long lastMoveMs = 0;
bool doStreamMove = (millis() - lastMoveMs >= subInterval);
if (doStreamMove)
lastMoveMs = millis();
// 3. 하단 매트릭스 모래알 업데이트
shuffleOrder();
for (int i = 0; i < 64; i++) {
int r = pixelOrder[i] / 8;
int c = pixelOrder[i] % 8;
if (botGrid[r][c]) {
// 낙하 중인 알(r+c < pileTopS)은 subInterval에 맞춰 이동하여 줄기 형성
if (r + c < pileTopS && !doStreamMove)
continue;
// 다이아몬드 방향(r+1, c+1)이 수직 아래
if (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;
}
}
}
}
}
// 4. 상단 -> 하단 낙하 (연속성 확보)
if (fallenCount < TOTAL_GRAINS && millis() >= nextSpawnMillis &&
!botGrid[0][0]) {
// 상단에서 가장 위(r+c가 작은 쪽, 표면)에 있는 모래알 찾기
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++;
nextSpawnMillis = millis() + INTERVAL_MS;
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");
}
}
}

'아두이노우노 R4' 카테고리의 다른 글
| WS2812 5050 RGB LED 구동 (0) | 2026.06.08 |
|---|---|
| MPU6050, MAX7219 8x8 Dot Matrix 아두이노 모래시계 소스코드 4 (0) | 2026.06.04 |
| MPU6050, MAX7219 8x8 Dot Matrix 아두이노 모래시계 소스코드 2 (0) | 2026.06.02 |
| MPU6050, MAX7219 8x8 Dot Matrix 아두이노 모래시계 소스코드 1 (0) | 2026.06.02 |
| MAX7219 8x8 도트 매트릭스 모듈 아두이노 구동 (0) | 2026.06.01 |
| 아두이노 UNO R4 WIFI를 사용하여 IoT 스마트 그리드를 구축하는 방법 (0) | 2026.04.09 |
| DIY 아두이노 휴대용 게임 콘솔 (레트로 게임 10개 포함) (0) | 2026.03.25 |
| 아두이노 나노 매터 보드는 전문가용 제피어 개발 플랫폼이 되었습니다. (0) | 2026.03.17 |
취업, 창업의 막막함, 외주 관리, 제품 부재!
당신의 고민은 무엇입니까? 현실과 동떨어진 교육, 실패만 반복하는 외주 계약,
아이디어는 있지만 구현할 기술이 없는 막막함.
우리는 알고 있습니다. 문제의 원인은 '명확한 학습, 실전 경험과 신뢰할 수 있는 기술력의 부재'에서 시작됩니다.
이제 고민을 멈추고, 캐어랩을 만나세요!
코딩(펌웨어), 전자부품과 디지털 회로설계, PCB 설계 제작, 고객(시장/수출) 발굴과 마케팅 전략으로 당신을 지원합니다.
제품 설계의 고수는 성공이 만든 게 아니라 실패가 만듭니다. 아이디어를 양산 가능한 제품으로!
귀사의 제품을 만드세요. 교육과 개발 실적으로 신뢰할 수 있는 파트너를 확보하세요.
캐어랩