MobBob: 아두이노 로봇 직접 만들기, 안드로이드 스마트폰으로 매끄럽게 제어
부품 및 소모품
| 부품 |
수량 |
| 아두이노 나노 R3 |
1 |
| HC-05 블루투스 모듈 |
1 |
| SG90 마이크로 서보 모터 |
4 |
| 4xAA 배터리 홀더 |
1 |
| AA 배터리 |
4 |
필수 도구 및 장비
앱 및 온라인 서비스
프로젝트 개요
웹사이트 지침에 따라 이 로봇을 만들었습니다. 아두이노와 안드로이드의 공생을 위한 훌륭한 아이디어입니다.
MobBob은 스마트폰으로 제어되는 로봇입니다. 스마트폰의 성능을 활용해 음성 인식과 컴퓨터 비전을 갖춘 걷고 말하는 로봇이 됩니다.
코드에서 다음을 설정해야 합니다:
- 빌드에 맞게 핀 변수 업데이트
- 서보 중심, 최소/최대 값 조정
- "FRONT_JOINT_HIPS"를 1 또는 -1로 설정 (엉덩이 서보 장착 방향에 따라). 필자는 서보 축을 MobBob 앞쪽에 장착했으며, 이 경우 1로 설정.
코드
코드 (아두이노)
/*
* =============================================================
* MobBob 제어 프로그램 - 소프트웨어 시리얼 블루투스 버전
* by Kevin Chan (aka Cevinius)
* =============================================================
*
* 이 프로그램은 시리얼 명령으로 MobBob을 제어합니다. 소프트웨어 시리얼 포트로 명령을 수신하며,
* 상단의 #define으로 핀을 정의합니다. 모든 아두이노 호환 보드 사용 가능.
*
* 주요 구성 요소: 부드러운 서보 애니메이션 프로그램, 시리얼 명령 파서.
*
* 애니메이션 시스템: 키프레임 배열을 부드럽게 재생. 큐 크기 1 (실행 중 1개, 대기 1개).
* 명령 파서: 시리얼로 수신한 명령 파싱 및 처리. 직접 서보 제어, 사전 정의 애니메이션/걷기 트리거.
*
* 일반 명령:
* - 준비/확인: <OK>
* - 서보 설정: <SV, time, leftHip, leftFoot, rightHip, rightFoot>
* - 정지/리셋: <ST>
* - 즉시 정지: <SI>
*
* 기본 걷기 명령:
* - 전진: <FW, #Times>, -1=연속, 0/생략=1회
* - 후진: <BW, #Times>
* - 좌회전: <LT, #Times>
* - 우회전: <RT, #Times>
*
* 재미 애니메이션:
* - 머리 흔들기: <SX, #Times>
* - 바운스: <BX, #Times>
* - 흔들기: <WX, #Times>, 좌/우: <WY>, <WZ>
* - 발 탭: <TX, #Times>, 좌/우: <TY>, <TZ>
* - 다리 흔들기: <LX, #Times>, 좌/우: <LY>, <LZ>
*
* 명령 완료 시 응답 문자열 전송. 정상 완료: <FW>, 중단 시: <FW,-1>
*/
#include <Servo.h>
#include <SoftwareSerial.h>
//----------------------------------------------------------------------------------
// 시리얼 통신 속도 - 블루투스 카드에 맞게 설정
//----------------------------------------------------------------------------------
#define SERIAL_SPEED 115200
const int rxPin = 11;
const int txPin = 12;
SoftwareSerial softwareSerial(rxPin, txPin);
//----------------------------------------------------------------------------------
// 아두이노 핀 설정 - 로봇에 맞게 변경
//----------------------------------------------------------------------------------
const int SERVO_LEFT_HIP = 5;
const int SERVO_LEFT_FOOT = 2;
const int SERVO_RIGHT_HIP = 3;
const int SERVO_RIGHT_FOOT = 4;
// 엉덩이 서보 장착 방향: 1=MobBob 스타일(앞쪽), -1=Bob 스타일(뒤쪽)
#define FRONT_JOINT_HIPS 1
//----------------------------------------------------------------------------------
// 서보 최대/최소/중심 상수 - 로봇에 맞게 설정
//----------------------------------------------------------------------------------
const int LEFT_HIP_CENTRE = 1580;
const int LEFT_HIP_MIN = LEFT_HIP_CENTRE - 500;
const int LEFT_HIP_MAX = LEFT_HIP_CENTRE + 500;
const int LEFT_FOOT_CENTRE = 1410;
const int LEFT_FOOT_MIN = LEFT_FOOT_CENTRE - 500;
const int LEFT_FOOT_MAX = LEFT_FOOT_CENTRE + 500;
const int RIGHT_HIP_CENTRE = 1500;
const int RIGHT_HIP_MIN = RIGHT_HIP_CENTRE - 500;
const int RIGHT_HIP_MAX = RIGHT_HIP_CENTRE + 500;
const int RIGHT_FOOT_CENTRE = 1465;
const int RIGHT_FOOT_MIN = RIGHT_FOOT_CENTRE - 500;
const int RIGHT_FOOT_MAX = RIGHT_FOOT_CENTRE + 500;
//------------------------------------------------------------------------------
// 관절 값 계산을 위한 헬퍼 함수
//------------------------------------------------------------------------------
int LeftHipCentre() { return LEFT_HIP_CENTRE; }
int LeftHipIn(int ms) { return LEFT_HIP_CENTRE + (FRONT_JOINT_HIPS * ms); }
int LeftHipOut(int ms) { return LEFT_HIP_CENTRE - (FRONT_JOINT_HIPS * ms); }
int RightHipCentre() { return RIGHT_HIP_CENTRE; }
int RightHipIn(int ms) { return RIGHT_HIP_CENTRE - (FRONT_JOINT_HIPS * ms); }
int RightHipOut(int ms) { return RIGHT_HIP_CENTRE + (FRONT_JOINT_HIPS * ms); }
int LeftFootFlat() { return LEFT_FOOT_CENTRE; }
int LeftFootUp(int ms) { return LEFT_FOOT_CENTRE - ms; }
int LeftFootDown(int ms) { return LEFT_FOOT_CENTRE + ms; }
int RightFootFlat() { return RIGHT_FOOT_CENTRE; }
int RightFootUp(int ms) { return RIGHT_FOOT_CENTRE + ms; }
int RightFootDown(int ms) { return RIGHT_FOOT_CENTRE - ms; }
//----------------------------------------------------------------------------------
// 키프레임 애니메이션 데이터 (걷기, 회전, 다양한 모션)
// 형식: {밀리초, leftHip, leftFoot, rightHip, rightFoot}
// -1 값은 해당 서보 위치를 유지하라는 의미
// 첫 번째 요소는 메타데이터: {프레임 수, 0, 0, 0, 0}
//----------------------------------------------------------------------------------
const int TWEEN_TIME_VALUE = 0;
const int LEFT_HIP_VALUE = 1;
const int LEFT_FOOT_VALUE = 2;
const int RIGHT_HIP_VALUE = 3;
const int RIGHT_FOOT_VALUE = 4;
const int FOOT_DELTA = 150;
const int HIP_DELTA = FRONT_JOINT_HIPS * 120;
// 기본 자세 (똑바로 서기)
int standStraightAnim[][5] = {
{ 1, 0, 0, 0, 0 },
{ 300, LeftHipCentre(), LeftFootFlat(), RightHipCentre(), RightFootFlat() }
};
// 전진 걷기
int walkForwardAnim[][5] = {
{ 8, 0, 0, 0, 0 },
{ 300, LeftHipCentre(), LeftFootUp(FOOT_DELTA), RightHipCentre(), RightFootDown(FOOT_DELTA) },
{ 300, LeftHipIn(HIP_DELTA), LeftFootUp(FOOT_DELTA), RightHipOut(HIP_DELTA), RightFootDown(FOOT_DELTA) },
{ 300, LeftHipIn(HIP_DELTA), LeftFootFlat(), RightHipOut(HIP_DELTA), RightFootFlat() },
{ 300, LeftHipIn(HIP_DELTA), LeftFootDown(FOOT_DELTA), RightHipOut(HIP_DELTA), RightFootUp(FOOT_DELTA) },
{ 300, LeftHipCentre(), LeftFootDown(FOOT_DELTA), RightHipCentre(), RightFootUp(FOOT_DELTA) },
{ 300, LeftHipOut(HIP_DELTA), LeftFootDown(FOOT_DELTA), RightHipIn(HIP_DELTA), RightFootUp(FOOT_DELTA) },
{ 300, LeftHipOut(HIP_DELTA), LeftFootFlat(), RightHipIn(HIP_DELTA), RightFootFlat() },
{ 300, LeftHipOut(HIP_DELTA), LeftFootUp(FOOT_DELTA), RightHipIn(HIP_DELTA), RightFootDown(FOOT_DELTA) }
};
// 후진 걷기
int walkBackwardAnim[][5] = {
{ 8, 0, 0, 0, 0 },
{ 300, LeftHipCentre(), LeftFootUp(FOOT_DELTA), RightHipCentre(), RightFootDown(FOOT_DELTA) },
{ 300, LeftHipOut(HIP_DELTA), LeftFootUp(FOOT_DELTA), RightHipIn(HIP_DELTA), RightFootDown(FOOT_DELTA) },
{ 300, LeftHipOut(HIP_DELTA), LeftFootFlat(), RightHipIn(HIP_DELTA), RightFootFlat() },
{ 300, LeftHipOut(HIP_DELTA), LeftFootDown(FOOT_DELTA), RightHipIn(HIP_DELTA), RightFootUp(FOOT_DELTA) },
{ 300, LeftHipCentre(), LeftFootDown(FOOT_DELTA), RightHipCentre(), RightFootUp(FOOT_DELTA) },
{ 300, LeftHipIn(HIP_DELTA), LeftFootDown(FOOT_DELTA), RightHipOut(HIP_DELTA), RightFootUp(FOOT_DELTA) },
{ 300, LeftHipIn(HIP_DELTA), LeftFootFlat(), RightHipOut(HIP_DELTA), RightFootFlat() },
{ 300, LeftHipIn(HIP_DELTA), LeftFootUp(FOOT_DELTA), RightHipOut(HIP_DELTA), RightFootDown(FOOT_DELTA) }
};
// 걷기 마무리 애니메이션 (기본 자세로 복귀)
int walkEndAnim[][5] = {
{ 2, 0, 0, 0, 0 },
{ 300, LeftHipCentre(), LeftFootUp(FOOT_DELTA), RightHipCentre(), RightFootDown(FOOT_DELTA) },
{ 300, LeftHipCentre(), LeftFootFlat(), RightHipCentre(), RightFootFlat() }
};
// 좌회전
int turnLeftAnim[][5] = {
{ 6, 0, 0, 0, 0 },
{ 300, LeftHipCentre(), LeftFootUp(FOOT_DELTA), RightHipCentre(), RightFootDown(FOOT_DELTA) },
{ 300, LeftHipIn(HIP_DELTA), LeftFootUp(FOOT_DELTA), RightHipIn(HIP_DELTA), RightFootDown(FOOT_DELTA) },
{ 300, LeftHipIn(HIP_DELTA), LeftFootFlat(), RightHipIn(HIP_DELTA), RightFootFlat() },
{ 300, LeftHipIn(HIP_DELTA), LeftFootDown(FOOT_DELTA), RightHipIn(HIP_DELTA), RightFootUp(FOOT_DELTA) },
{ 300, LeftHipCentre(), LeftFootDown(FOOT_DELTA), RightHipCentre(), RightFootUp(FOOT_DELTA) },
{ 300, LeftHipCentre(), LeftFootFlat(), RightHipCentre(), RightFootFlat() }
};
// 우회전
int turnRightAnim[][5] = {
{ 6, 0, 0, 0, 0 },
{ 300, LeftHipCentre(), LeftFootDown(FOOT_DELTA), RightHipCentre(), RightFootUp(FOOT_DELTA) },
{ 300, LeftHipIn(HIP_DELTA), LeftFootDown(FOOT_DELTA), RightHipIn(HIP_DELTA), RightFootUp(FOOT_DELTA) },
{ 300, LeftHipIn(HIP_DELTA), LeftFootFlat(), RightHipIn(HIP_DELTA), RightFootFlat() },
{ 300, LeftHipIn(HIP_DELTA), LeftFootUp(FOOT_DELTA), RightHipIn(HIP_DELTA), RightFootDown(FOOT_DELTA) },
{ 300, LeftHipCentre(), LeftFootUp(FOOT_DELTA), RightHipCentre(), RightFootDown(FOOT_DELTA) },
{ 300, LeftHipCentre(), LeftFootFlat(), RightHipCentre(), RightFootFlat() }
};
// 머리 흔들기
int shakeHeadAnim[][5] = {
{ 4, 0, 0, 0, 0 },
{ 150, LeftHipOut(HIP_DELTA), LeftFootFlat(), RightHipIn(HIP_DELTA), RightFootFlat() },
{ 150, LeftHipCentre(), LeftFootFlat(), RightHipCentre(), RightFootFlat() },
{ 150, LeftHipIn(HIP_DELTA), LeftFootFlat(), RightHipOut(HIP_DELTA), RightFootFlat() },
{ 150, LeftHipCentre(), LeftFootFlat(), RightHipCentre(), RightFootFlat() }
};
// 흔들기 (Wobble)
int wobbleAnim[][5] = {
{ 4, 0, 0, 0, 0 },
{ 300, LeftHipCentre(), LeftFootUp(FOOT_DELTA), RightHipCentre(), RightFootDown(FOOT_DELTA) },
{ 300, LeftHipCentre(), LeftFootFlat(), RightHipCentre(), RightFootFlat() },
{ 300, LeftHipCentre(), LeftFootDown(FOOT_DELTA), RightHipCentre(), RightFootUp(FOOT_DELTA) },
{ 300, LeftHipCentre(), LeftFootFlat(), RightHipCentre(), RightFootFlat() }
};
// 좌측 흔들기
int wobbleLeftAnim[][5] = {
{ 2, 0, 0, 0, 0 },
{ 300, LeftHipCentre(), LeftFootUp(FOOT_DELTA), RightHipCentre(), RightFootDown(FOOT_DELTA) },
{ 300, LeftHipCentre(), LeftFootFlat(), RightHipCentre(), RightFootFlat() }
};
// 우측 흔들기
int wobbleRightAnim[][5] = {
{ 2, 0, 0, 0, 0 },
{ 300, LeftHipCentre(), LeftFootDown(FOOT_DELTA), RightHipCentre(), RightFootUp(FOOT_DELTA) },
{ 300, LeftHipCentre(), LeftFootFlat(), RightHipCentre(), RightFootFlat() }
};
// 양발 탭
int tapFeetAnim[][5] = {
{ 2, 0, 0, 0, 0 },
{ 500, LeftHipCentre(), LeftFootUp(FOOT_DELTA), RightHipCentre(), RightFootUp(FOOT_DELTA) },
{ 500, LeftHipCentre(), LeftFootFlat(), RightHipCentre(), RightFootFlat() }
};
// 좌측 발 탭
int tapLeftFootAnim[][5] = {
{ 2, 0, 0, 0, 0 },
{ 500, LeftHipCentre(), LeftFootUp(FOOT_DELTA), RightHipCentre(), RightFootFlat() },
{ 500, LeftHipCentre(), LeftFootFlat(), RightHipCentre(), RightFootFlat() }
};
// 우측 발 탭
int tapRightFootAnim[][5] = {
{ 2, 0, 0, 0, 0 },
{ 500, LeftHipCentre(), LeftFootFlat(), RightHipCentre(), RightFootUp(FOOT_DELTA) },
{ 500, LeftHipCentre(), LeftFootFlat(), RightHipCentre(), RightFootFlat() }
};
// 바운스
int bounceAnim[][5] = {
{ 2, 0, 0, 0, 0 },
{ 500, LeftHipCentre(), LeftFootDown(300), RightHipCentre(), RightFootDown(300) },
{ 500, LeftHipCentre(), LeftFootFlat(), RightHipCentre(), RightFootFlat() }
};
// 다리 흔들기
int shakeLegsAnim[][5] = {
{ 14, 0, 0, 0, 0 },
{ 300, LeftHipCentre(), LeftFootUp(FOOT_DELTA), RightHipCentre(), RightFootDown(FOOT_DELTA) },
{ 100, LeftHipCentre(), LeftFootUp(FOOT_DELTA), RightHipIn(HIP_DELTA), RightFootDown(FOOT_DELTA) },
{ 100, LeftHipCentre(), LeftFootUp(FOOT_DELTA), RightHipCentre(), RightFootDown(FOOT_DELTA) },
{ 100, LeftHipCentre(), LeftFootUp(FOOT_DELTA), RightHipOut(HIP_DELTA), RightFootDown(FOOT_DELTA) },
{ 100, LeftHipCentre(), LeftFootUp(FOOT_DELTA), RightHipCentre(), RightFootDown(FOOT_DELTA) },
{ 100, LeftHipCentre(), LeftFootUp(FOOT_DELTA), RightHipIn(HIP_DELTA), RightFootDown(FOOT_DELTA) },
{ 100, LeftHipCentre(), LeftFootUp(FOOT_DELTA), RightHipCentre(), RightFootDown(FOOT_DELTA) },
{ 300, LeftHipCentre(), LeftFootFlat(), RightHipCentre(), RightFootFlat() },
{ 300, LeftHipCentre(), LeftFootDown(FOOT_DELTA), RightHipCentre(), RightFootUp(FOOT_DELTA) },
{ 100, LeftHipIn(HIP_DELTA), LeftFootDown(FOOT_DELTA), RightHipCentre(), RightFootUp(FOOT_DELTA) },
{ 100, LeftHipCentre(), LeftFootDown(FOOT_DELTA), RightHipCentre(), RightFootUp(FOOT_DELTA) },
{ 100, LeftHipOut(HIP_DELTA), LeftFootDown(FOOT_DELTA), RightHipCentre(), RightFootUp(FOOT_DELTA) },
{ 100, LeftHipCentre(), LeftFootDown(FOOT_DELTA), RightHipCentre(), RightFootUp(FOOT_DELTA) },
{ 300, LeftHipCentre(), LeftFootFlat(), RightHipCentre(), RightFootFlat() }
};
// 좌측 다리 흔들기
int shakeLeftLegAnim[][5] = {
{ 12, 0, 0, 0, 0 },
{ 300, LeftHipCentre(), LeftFootDown(FOOT_DELTA), RightHipCentre(), RightFootUp(FOOT_DELTA) },
{ 100, LeftHipIn(HIP_DELTA), LeftFootDown(FOOT_DELTA), RightHipCentre(), RightFootUp(FOOT_DELTA) },
{ 100, LeftHipCentre(), LeftFootDown(FOOT_DELTA), RightHipCentre(), RightFootUp(FOOT_DELTA) },
{ 100, LeftHipOut(HIP_DELTA), LeftFootDown(FOOT_DELTA), RightHipCentre(), RightFootUp(FOOT_DELTA) },
{ 100, LeftHipCentre(), LeftFootDown(FOOT_DELTA), RightHipCentre(), RightFootUp(FOOT_DELTA) },
{ 100, LeftHipIn(HIP_DELTA), LeftFootDown(FOOT_DELTA), RightHipCentre(), RightFootUp(FOOT_DELTA) },
{ 100, LeftHipCentre(), LeftFootDown(FOOT_DELTA), RightHipCentre(), RightFootUp(FOOT_DELTA) },
{ 100, LeftHipOut(HIP_DELTA), LeftFootDown(FOOT_DELTA), RightHipCentre(), RightFootUp(FOOT_DELTA) },
{ 100, LeftHipCentre(), LeftFootDown(FOOT_DELTA), RightHipCentre(), RightFootUp(FOOT_DELTA) },
{ 100, LeftHipIn(HIP_DELTA), LeftFootDown(FOOT_DELTA), RightHipCentre(), RightFootUp(FOOT_DELTA) },
{ 100, LeftHipCentre(), LeftFootDown(FOOT_DELTA), RightHipCentre(), RightFootUp(FOOT_DELTA) },
{ 300, LeftHipCentre(), LeftFootFlat(), RightHipCentre(), RightFootFlat() }
};
// 우측 다리 흔들기
int shakeRightLegAnim[][5] = {
{ 12, 0, 0, 0, 0 },
{ 300, LeftHipCentre(), LeftFootUp(FOOT_DELTA), RightHipCentre(), RightFootDown(FOOT_DELTA) },
{ 100, LeftHipCentre(), LeftFootUp(FOOT_DELTA), RightHipIn(HIP_DELTA), RightFootDown(FOOT_DELTA) },
{ 100, LeftHipCentre(), LeftFootUp(FOOT_DELTA), RightHipCentre(), RightFootDown(FOOT_DELTA) },
{ 100, LeftHipCentre(), LeftFootUp(FOOT_DELTA), RightHipOut(HIP_DELTA), RightFootDown(FOOT_DELTA) },
{ 100, LeftHipCentre(), LeftFootUp(FOOT_DELTA), RightHipCentre(), RightFootDown(FOOT_DELTA) },
{ 100, LeftHipCentre(), LeftFootUp(FOOT_DELTA), RightHipIn(HIP_DELTA), RightFootDown(FOOT_DELTA) },
{ 100, LeftHipCentre(), LeftFootUp(FOOT_DELTA), RightHipCentre(), RightFootDown(FOOT_DELTA) },
{ 100, LeftHipCentre(), LeftFootUp(FOOT_DELTA), RightHipOut(HIP_DELTA), RightFootDown(FOOT_DELTA) },
{ 100, LeftHipCentre(), LeftFootUp(FOOT_DELTA), RightHipCentre(), RightFootDown(FOOT_DELTA) },
{ 100, LeftHipCentre(), LeftFootUp(FOOT_DELTA), RightHipIn(HIP_DELTA), RightFootDown(FOOT_DELTA) },
{ 100, LeftHipCentre(), LeftFootUp(FOOT_DELTA), RightHipCentre(), RightFootDown(FOOT_DELTA) },
{ 300, LeftHipCentre(), LeftFootFlat(), RightHipCentre(), RightFootFlat() }
};
//----------------------------------------------------------------------------------
// 서보 직접 제어용 동적 애니메이션 데이터
//----------------------------------------------------------------------------------
int setServosAnim1[][5] = {
{ 1, 0, 0, 0, 0 },
{ 0, LeftHipCentre(), LeftFootFlat(), RightHipCentre(), RightFootFlat() }
};
int setServosAnim2[][5] = {
{ 1, 0, 0, 0, 0 },
{ 0, LeftHipCentre(), LeftFootFlat(), RightHipCentre(), RightFootFlat() }
};
//----------------------------------------------------------------------------------
// 서보 변수 및 상태 변수
//----------------------------------------------------------------------------------
Servo servoLeftHip;
Servo servoLeftFoot;
Servo servoRightHip;
Servo servoRightFoot;
const int millisBetweenAnimUpdate = 20;
long timeAtLastAnimUpdate;
int (*currAnim)[5];
int (*finishAnim)[5];
long timeAtStartOfFrame;
int targetFrame;
int animNumLoops;
char animCompleteStr[3] = "--";
bool animInProgress;
int (*nextAnim)[5];
int (*nextFinishAnim)[5];
int nextAnimNumLoops;
char nextAnimCompleteStr[3] = "--";
bool interruptInProgressAnim;
int currLeftHip, currLeftFoot, currRightHip, currRightFoot;
int startLeftHip, startLeftFoot, startRightHip, startRightFoot;
//===============================================================================
// 파서 관련
//===============================================================================
const char START_CHAR = '<';
const char END_CHAR = '>';
const char SEP_CHAR = ',';
const int PARSER_WAITING = 0;
const int PARSER_COMMAND = 1;
const int PARSER_PARAM1 = 2;
const int PARSER_PARAM2 = 3;
const int PARSER_PARAM3 = 4;
const int PARSER_PARAM4 = 5;
const int PARSER_PARAM5 = 6;
const int PARSER_EXECUTE = 7;
int currParserState = PARSER_WAITING;
char currCmd[3] = "--";
int currCmdIndex;
const int CMD_LENGTH = 2;
int currParam1Val, currParam2Val, currParam3Val, currParam4Val, currParam5Val;
int currParamIndex;
boolean currParamNegative;
const int MAX_PARAM_LENGTH = 6;
//===============================================================================
// Arduino setup() and loop()
//===============================================================================
void setup() {
softwareSerial.begin(SERIAL_SPEED);
servoLeftHip.attach(SERVO_LEFT_HIP, LEFT_HIP_MIN, LEFT_HIP_MAX);
servoLeftFoot.attach(SERVO_LEFT_FOOT, LEFT_FOOT_MIN, LEFT_FOOT_MAX);
servoRightHip.attach(SERVO_RIGHT_HIP, RIGHT_HIP_MIN, RIGHT_HIP_MAX);
servoRightFoot.attach(SERVO_RIGHT_FOOT, RIGHT_FOOT_MIN, RIGHT_FOOT_MAX);
setup_Parser();
setup_Animation();
}
void loop() {
loop_Parser();
loop_Animation();
}
// 파서 설정 및 루프, 애니메이션 처리 함수들 생략 (원문 참조)
// 전체 코드는 원문 길이 관계로 핵심 부분만 표시했습니다.
// 완전한 코드는 원본 프로젝트 페이지 또는 깃허브에서 확인하세요.
회로도
