#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <math.h>
// 시스템 설정 및 하드웨어 핀 정의
namespace Config
{
constexpr int SCREEN_WIDTH = 128;
constexpr int SCREEN_HEIGHT = 64;
constexpr int OLED_RESET = -1;
constexpr uint8_t SCREEN_ADDRESS = 0x3C;
constexpr int LED1_PIN = 2;
constexpr int LED2_PIN = 3;
constexpr unsigned long TOGGLE_INTERVAL = 2000; // LED 상태 전환 간격 (ms)
constexpr float ANIMATION_SPEED = 15.0; // 애니메이션 증가 속도
}
// 디스플레이 UI 레이아웃 상수
namespace DisplayLayout
{
constexpr int LABEL_Y = 5;
constexpr int CIRCLE_Y = 40;
constexpr int CIRCLE_RADIUS = 18;
constexpr int LED1_CENTER_X = 32;
constexpr int LED2_CENTER_X = 96;
}
Adafruit_SSD1306 display(Config::SCREEN_WIDTH, Config::SCREEN_HEIGHT, &Wire, Config::OLED_RESET);
// 각 LED의 상태 및 애니메이션 진행도를 관리하는 구조체
struct LedDisplayState
{
bool isActive = false;
float progress = 0.0; // 0.0 ~ 360.0 (각도)
};
struct SystemState
{
LedDisplayState led1;
LedDisplayState led2;
unsigned long lastToggleTime = 0;
} state;
// 함수 선언
void updateSystem();
void updateAnimation(LedDisplayState &led);
void refreshDisplay();
void drawLedUI(int centerX, const char *label, LedDisplayState &ledState);
void drawArc(int x, int y, int r, float endAngle, uint16_t color);
void setup()
{
Serial.begin(115200);
pinMode(Config::LED1_PIN, OUTPUT);
pinMode(Config::LED2_PIN, OUTPUT);
if (!display.begin(SSD1306_SWITCHCAPVCC, Config::SCREEN_ADDRESS))
{
Serial.println(F("SSD1306 allocation failed"));
for (;;)
;
}
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setTextSize(1);
}
void loop()
{
updateSystem(); // 1. 하드웨어 및 애니메이션 상태 업데이트
refreshDisplay(); // 2. 화면 출력
}
void updateSystem()
{
unsigned long currentTime = millis();
// 1. 테스트용 LED 상태 토글 로직
if (currentTime - state.lastToggleTime >= Config::TOGGLE_INTERVAL)
{
state.led1.isActive = !state.led1.isActive;
state.led2.isActive = !state.led1.isActive;
state.lastToggleTime = currentTime;
digitalWrite(Config::LED1_PIN, state.led1.isActive);
digitalWrite(Config::LED2_PIN, state.led2.isActive);
}
// 2. 애니메이션 진행도 업데이트
updateAnimation(state.led1);
updateAnimation(state.led2);
}
void updateAnimation(LedDisplayState &led)
{
if (led.isActive)
{
if (led.progress < 360.0)
{
led.progress += Config::ANIMATION_SPEED;
if (led.progress > 360.0)
led.progress = 360.0;
}
}
else
{
// OFF일 때는 진행도를 즉시 초기화하거나 서서히 줄일 수 있음
led.progress = 0.0;
}
}
void refreshDisplay()
{
display.clearDisplay();
drawLedUI(DisplayLayout::LED1_CENTER_X, "LED 1", state.led1);
drawLedUI(DisplayLayout::LED2_CENTER_X, "LED 2", state.led2);
display.display();
}
void drawLedUI(int centerX, const char *label, LedDisplayState &ledState)
{
int16_t x1, y1;
uint16_t w, h;
// 1. 라벨 출력
display.setTextSize(1);
display.getTextBounds(label, 0, 0, &x1, &y1, &w, &h);
display.setCursor(centerX - (w / 2), DisplayLayout::LABEL_Y);
display.print(label);
// 2. 애니메이션 원(Arc) 그리기
if (ledState.progress > 0)
{
drawArc(centerX, DisplayLayout::CIRCLE_Y, DisplayLayout::CIRCLE_RADIUS, ledState.progress, SSD1306_WHITE);
}
// 3. 상태 텍스트 출력
const char *statusText = ledState.isActive ? "ON" : "OFF";
display.getTextBounds(statusText, 0, 0, &x1, &y1, &w, &h);
display.setCursor(centerX - (w / 2), DisplayLayout::CIRCLE_Y - (h / 2));
display.print(statusText);
}
/**
* 원형 호(Arc)를 그리는 함수
* endAngle: 0 ~ 360도
*/
void drawArc(int x, int y, int r, float endAngle, uint16_t color)
{
// 원의 두께를 2픽셀로 표현하기 위해 두 개의 반지름(r, r-1)에 대해 그립니다.
for (int thickness = 0; thickness < 2; thickness++)
{
int currentR = r - thickness;
for (float i = 0; i <= endAngle; i += 10.0)
{
float angleRad = (i - 90.0) * (PI / 180.0);
int px = x + cos(angleRad) * currentR;
int py = y + sin(angleRad) * currentR;
if (i > 0)
{
float prevAngleRad = (i - 10.0 - 90.0) * (PI / 180.0);
int ppx = x + cos(prevAngleRad) * currentR;
int ppy = y + sin(prevAngleRad) * currentR;
display.drawLine(ppx, ppy, px, py, color);
}
// 루프의 마지막 단계에서 정확한 endAngle까지 선을 긋습니다.
if (i + 10.0 > endAngle && i < endAngle)
{
float finalRad = (endAngle - 90.0) * (PI / 180.0);
int fpx = x + cos(finalRad) * currentR;
int fpy = y + sin(finalRad) * currentR;
display.drawLine(px, py, fpx, fpy, color);
}
}
}
}