#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <ezButton.h>
#define ENCODER_PIN_A 2
#define ENCODER_PIN_B 3
#define ENCODER_BTN_PIN 4
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
volatile long encoderPosition = 0;
volatile int lastEncoded = 0;
int menuIndex = 0; // 0:W, 1:V, 2:A
const unsigned long blinkInterval = 500; // 0.5초
ezButton encoderBtn(ENCODER_BTN_PIN);
// W 그래프 관련 변수 선언
#define W_GRAPH_WIDTH (SCREEN_WIDTH / 2)
#define W_GRAPH_HEIGHT SCREEN_HEIGHT
#define W_GRAPH_POINTS 64
float wValues[W_GRAPH_POINTS] = {0};
int wIndex = 0;
bool inWGraphScreen = false;
void handleEncoder()
{
int MSB = digitalRead(ENCODER_PIN_A);
int LSB = digitalRead(ENCODER_PIN_B);
int encoded = (MSB << 1) | LSB;
int sum = (lastEncoded << 2) | encoded;
if (sum == 0b1101 || sum == 0b0100 || sum == 0b0010 || sum == 0b1011)
encoderPosition++;
if (sum == 0b1110 || sum == 0b0111 || sum == 0b0001 || sum == 0b1000)
encoderPosition--;
lastEncoded = encoded;
}
void drawMenu()
{
display.clearDisplay();
// ----- 왼쪽 공식 그림 -----
int x_offset = -28;
int cx = 64 + x_offset, cy = 32, r = 30;
display.drawCircle(cx, cy, r, SSD1306_WHITE);
display.drawLine(cx - 29, cy, cx + 29, cy, SSD1306_WHITE);
display.drawLine(cx, cy, cx, cy + 30, SSD1306_WHITE);
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE);
display.setCursor(cx - 5, cy - 20);
display.print("V");
display.setCursor(cx - 15, cy + 5);
display.print("I");
display.setCursor(cx + 7, cy + 5);
display.print("R");
// ----- 오른쪽 W/V/A 박스 -----
int box_height = 20;
int box_width = 24;
int box_x_offset = -5;
int text_x_offset = 7;
int text_y_offset = 2;
int x = SCREEN_WIDTH - box_width - 4 + box_x_offset;
int y_top = 2;
int y_mid = y_top + box_height;
int y_bot = y_mid + box_height;
// W 박스
if (menuIndex == 0)
display.fillRect(x, y_top, box_width, box_height, SSD1306_WHITE);
else
display.drawRect(x, y_top, box_width, box_height, SSD1306_WHITE);
display.setTextColor((menuIndex == 0) ? SSD1306_BLACK : SSD1306_WHITE);
display.setCursor(x + text_x_offset, y_top + text_y_offset);
display.print("W");
// V 박스
if (menuIndex == 1)
display.fillRect(x, y_mid, box_width, box_height, SSD1306_WHITE);
else
display.drawRect(x, y_mid, box_width, box_height, SSD1306_WHITE);
display.setTextColor((menuIndex == 1) ? SSD1306_BLACK : SSD1306_WHITE);
display.setCursor(x + text_x_offset, y_mid + text_y_offset);
display.print("V");
// A 박스
if (menuIndex == 2)
display.fillRect(x, y_bot, box_width, box_height, SSD1306_WHITE);
else
display.drawRect(x, y_bot, box_width, box_height, SSD1306_WHITE);
display.setTextColor((menuIndex == 2) ? SSD1306_BLACK : SSD1306_WHITE);
display.setCursor(x + text_x_offset, y_bot + text_y_offset);
display.print("A");
display.display();
display.setTextColor(SSD1306_WHITE); // 다음 draw를 위해 복원
}
void drawWGraphScreen()
{
display.clearDisplay();
// X,Y축 그리기
display.drawRect(0, 0, W_GRAPH_WIDTH, W_GRAPH_HEIGHT, SSD1306_WHITE);
display.drawLine(0, W_GRAPH_HEIGHT - 1, W_GRAPH_WIDTH, W_GRAPH_HEIGHT - 1, SSD1306_WHITE); // X축
display.drawLine(0, 0, 0, W_GRAPH_HEIGHT, SSD1306_WHITE); // Y축
// W 값 라인그래프 그리기
for (int i = 1; i < W_GRAPH_POINTS; i++)
{
int idx0 = (wIndex + i - 1) % W_GRAPH_POINTS;
int idx1 = (wIndex + i) % W_GRAPH_POINTS;
int y0 = W_GRAPH_HEIGHT - 1 - (int)(wValues[idx0] * (W_GRAPH_HEIGHT - 2));
int y1 = W_GRAPH_HEIGHT - 1 - (int)(wValues[idx1] * (W_GRAPH_HEIGHT - 2));
display.drawLine(i - 1, y0, i, y1, SSD1306_WHITE);
}
// 오른쪽 영역은 미지정(비워둠)
display.display();
}
void setup()
{
pinMode(ENCODER_PIN_A, INPUT_PULLUP);
pinMode(ENCODER_PIN_B, INPUT_PULLUP);
int MSB = digitalRead(ENCODER_PIN_A);
int LSB = digitalRead(ENCODER_PIN_B);
lastEncoded = (MSB << 1) | LSB;
attachInterrupt(digitalPinToInterrupt(ENCODER_PIN_A), handleEncoder, CHANGE);
attachInterrupt(digitalPinToInterrupt(ENCODER_PIN_B), handleEncoder, CHANGE);
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
drawMenu();
encoderBtn.setDebounceTime(50); // 50ms 디바운스
}
void loop()
{
static long lastPos = 0;
static unsigned long lastBlink = 0;
static bool blinkState = false;
static unsigned long wScreenBtnPressedTime = 0;
encoderBtn.loop();
// 메뉴에서 버튼 눌림 감지 (ezButton 사용)
if (!inWGraphScreen && menuIndex == 0 && encoderBtn.isPressed())
{
inWGraphScreen = true;
display.clearDisplay();
display.display();
}
// 메뉴 화면에서 엔코더 회전 처리
if (!inWGraphScreen) {
long filteredEncoderPos = encoderPosition / 4;
if (filteredEncoderPos != lastPos) {
if (filteredEncoderPos > lastPos) {
menuIndex = (menuIndex + 1) % 3;
} else {
menuIndex = (menuIndex + 2) % 3; // (menuIndex-1+3)%3
}
lastPos = filteredEncoderPos;
drawMenu();
}
// 깜빡임 타이머
if (millis() - lastBlink > blinkInterval) {
blinkState = !blinkState;
lastBlink = millis();
drawMenu();
}
}
if (inWGraphScreen) {
// W값 샘플링 (여기선 예시로 사인파)
float w = (sin(millis() / 1000.0) + 1.0) / 2.0; // 0~1 범위
wValues[wIndex] = w;
wIndex = (wIndex + 1) % W_GRAPH_POINTS;
drawWGraphScreen();
// 버튼 길게 누르면 메뉴로 복귀 (getHoldTime() → isPressed()/isReleased() 조합)
if (encoderBtn.isPressed()) {
wScreenBtnPressedTime = millis();
}
if (encoderBtn.isReleased()) {
if (millis() - wScreenBtnPressedTime > 1000) { // 1초 이상 눌렀을 때
inWGraphScreen = false;
drawMenu();
delay(300); // 복귀시 디바운스
}
}
}
}
Loading
ssd1306
ssd1306