본문 바로가기

Projects

[Arduino] 아두이노로 자취방을 꾸며보자 - arduino diy led strip

24.12.02 집필 시작

24.12.20 2차 집필 및 업로드. 사진 및 링크를 아직 업로드 안함

24.12.24 최종 사진 업로드 및 코드블럭 접기

 

--- INDEX ---

0. 준비물
1. 코딩
2. 회로
3. 모델링
4. 3d프린팅
5. 완료

 

--- --- --- ---

 

0. 준비물


 우선 내가 만들고싶은건, 내 술장을 예쁘게 꾸며줄 LED다. LED는 자고로 여러가지 반짝임이 있는 편이 예쁘다. 완전한 꺼졌다 켜짐이 아니더라도, 밝기 조절 만으로도 단조로움을 해결해 줄 수 있다. 따라서 이를 변화시킬 수 있는 단순한 구조물을 만들어 보았다.

 

 

 0.1 LED

 우선 내가 만들고싶은건, 내 술장을 예쁘게 꾸며줄 LED다.

LED는 직접 만들고 뜯어고치는거 보다 다이소에 3처넌이면 엄청많은 led strip을 판매하니, 이걸 쟁여두자.

이게 3처넌밖에 안한다

 

 0.2 아두이노

 집안에 하나씩 굴러다니는 아두이노 나노를 준비한다. -> 집에서 굴러먹던게 사실 죽은거였다니.. 왠종일 뻘짓만 하다가 아두이노 우노로 돌아왔다. 덕분에 사이즈가 엄청 커진건 비밀..

 

 0.3 프린팅 재료

 흰색 PLA+와 3d프린터를 준비한다.

필자의 경우는, 뱀부 X1 카본 모델을 이용했다.

 

 0.4 arduino oled

  선이 많은건 복잡하니 SPI말고 I2C가 지원되는 아두이노 oled를 준비한다.

 

 

1. 코딩

 

사실 코딩은 그닥 어려운게 아니라, 단순한 arduino PWM구현밖에 없다. 버튼을 눌렀을 때, 모드를 변경해 줄 디바운싱은 덤.

 

 

 

우선 led strip의 동작조건을 확인해봤다.

aa건전지 두개에 해당하는 3V에서는 0.26a밖에 사용되지 않는다. 하지만 아두이노의 출력은 5v 500mA 이기 때문에 5V를 넣어보니, cc가 걸리더라.. cc를 풀어주니 2.38A까지 치솟으며 매우 밝아졌다.

 

이런 경우는 led의 원래 출력보다 낮은 전압을 썼기 때문에 AA건전지 두개 만으로도 적절한 전류가 받쳐줬던 것 같다. 원래 led는 전압을 높이면 암페어가 더 가파르게 올라가는 것을 알 수 있는데, 이러한 안전장치가 없는 경우에는 과열되거나 깨질 수 있으니 조심한다.

 

 

암튼, 필자는 5개의 모드를 만들었다.

그냥 켜져있는것과 깜빡이는 속도를 조절했다.

일단 코드 전문을 올려보자면,

 

 

 

 

 

 

코드블럭은 너무 길어, 글이 너무 길어보인다. 따라서 접어두었다.

아래의 더보기를 누르면 코드가 나온다.

더보기
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

#define OLED_RESET     -1 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

#define NUMFLAKES     10 // Number of snowflakes in the animation example

#define LOGO_HEIGHT   16
#define LOGO_WIDTH    16

#define XPOS   0 // Indexes into the 'icons' array in function below
#define YPOS   1
#define DELTAY 2

const int mode_changer = 2;
const int oled_changer = 3;
const int led = 11;

bool btn_flag = false;
bool oled_flag = true;
unsigned long current_time = millis();
unsigned long prev_time = millis();
unsigned long current_time_led = millis();
unsigned long prev_time_led = millis();
unsigned long prev_mode_change_time = millis();
unsigned long current_mode_change_time = millis();

volatile int finite_state = 0;

bool fliker_state = HIGH;
bool increasing = true;
bool char_flag = false;
int char_flag_cnt = 0;

int brightness = 0;

int8_t f, icons[NUMFLAKES][3];
static const unsigned char PROGMEM logo_bmp[] =
{ 0b00000000, 0b11000000,
  0b00000001, 0b11000000,
  0b00000001, 0b11000000,
  0b00000011, 0b11100000,
  0b11110011, 0b11100000,
  0b11111110, 0b11111000,
  0b01111110, 0b11111111,
  0b00110011, 0b10011111,
  0b00011111, 0b11111100,
  0b00001101, 0b01110000,
  0b00011011, 0b10100000,
  0b00111111, 0b11100000,
  0b00111111, 0b11110000,
  0b01111100, 0b11110000,
  0b01110000, 0b01110000,
  0b00000000, 0b00110000 };

void setup() {
  pinMode(led,OUTPUT);
  pinMode(mode_changer, INPUT);
  pinMode(oled_changer, INPUT);
  attachInterrupt(digitalPinToInterrupt(mode_changer), interrupt_mode, RISING);
  attachInterrupt(digitalPinToInterrupt(oled_changer), interrupt_oled, RISING);
  Serial.begin(9600);
  if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Don't proceed, loop forever
  }
  for(f=0; f< NUMFLAKES; f++) {
    icons[f][XPOS]   = random(1 - LOGO_WIDTH, display.width());
    icons[f][YPOS]   = -LOGO_HEIGHT;
    icons[f][DELTAY] = random(1, 6);
  }
  display.display();
  delay(2000); // Pause for 2 seconds
  display.clearDisplay();
  Serial.println("init clear");

}

void loop() {
  //0 is always ON mode ( full power )
  //1 is always ON mode ( half power )
  //2 is slow fliker mode
  //3 is more slow fliker mode
  //4 is super slow non off fliker mode
  if (finite_state == 0) {
    digitalWrite(led,HIGH);
    btn_flag = false;
    Serial.println("state 0 ");
    current_mode_change_time = millis();
    if ((current_mode_change_time - prev_mode_change_time) <= 1500) {
        display.clearDisplay(); // Clear the display buffer
        display.display();
        delay(1);
        
        display.clearDisplay(); // Clear the display buffer
        display.setTextSize(2);             // Normal 1:1 pixel scale
        display.setTextColor(SSD1306_WHITE);        // Draw white text
        display.setCursor(0,0);             // Start at top-left corner
        display.println(F("State0"));
        display.setTextSize(2);             // Normal 1:1 pixel scale
        display.setTextColor(SSD1306_WHITE);        // Draw white text
        display.setCursor(0,16);             // Start at top-left corner
        display.println(F("Always ON"));
        display.setCursor(0,32);             // Start at top-left corner
        display.println(F("FULL POWER"));
      
      
        display.setTextColor(SSD1306_WHITE);
        
        display.display();
        char_flag = true;
        delay(1500);
    }
    else if (oled_flag == true) {
      display.clearDisplay(); // Clear the display buffer

      // Draw each snowflake:
      for(f=0; f< NUMFLAKES; f++) {
        display.drawBitmap(icons[f][XPOS], icons[f][YPOS], logo_bmp, LOGO_WIDTH, LOGO_HEIGHT, SSD1306_WHITE);
      }

      display.display(); // Show the display buffer on the screen
      // delay(200);        // Pause for 1/10 second

      // Then update coordinates of each flake...
      for(f=0; f< NUMFLAKES; f++) {
        icons[f][YPOS] += icons[f][DELTAY];
        // If snowflake is off the bottom of the screen...
        if (icons[f][YPOS] >= display.height()) {
          // Reinitialize to a random position, just off the top
          icons[f][XPOS]   = random(1 - LOGO_WIDTH, display.width());
          icons[f][YPOS]   = -LOGO_HEIGHT;
          icons[f][DELTAY] = random(1, 6);
        }
      }

    }
    else {
      display.clearDisplay(); // Clear the display buffer
      delay(10);
      // display.display();    
      display.fillRect(0, 0, display.width(), display.height(), SSD1306_BLACK);
      delay(10);
      display.display();    
    }

  }
  else if(finite_state == 1) {
    Serial.println("state 1 ");
    analogWrite(led,127);
    btn_flag = false;
    current_mode_change_time = millis();
    if ((current_mode_change_time - prev_mode_change_time) <= 1500) {
        display.clearDisplay(); // Clear the display buffer
        display.display();
        delay(1);
        
        display.clearDisplay(); // Clear the display buffer
        display.setTextSize(2);             // Normal 1:1 pixel scale
        display.setTextColor(SSD1306_WHITE);        // Draw white text
        display.setCursor(0,0);             // Start at top-left corner
        display.println(F("State1"));
        display.setTextSize(2);             // Normal 1:1 pixel scale
        display.setTextColor(SSD1306_WHITE);        // Draw white text
        display.setCursor(0,16);             // Start at top-left corner
        display.println(F("Always ON"));
        display.setCursor(0,32);             // Start at top-left corner
        display.println(F("HALF POWER"));
      
      
        display.setTextColor(SSD1306_WHITE);
        
        display.display();
        char_flag = true;
        delay(1500);
    }
    else if (oled_flag == true) {
      char_flag = false;
      display.clearDisplay(); // Clear the display buffer
      Serial.println("in else if");

      // Draw each snowflake:
      for(f=0; f< NUMFLAKES; f++) {
        display.drawBitmap(icons[f][XPOS], icons[f][YPOS], logo_bmp, LOGO_WIDTH, LOGO_HEIGHT, SSD1306_WHITE);
      }

      display.display(); // Show the display buffer on the screen
      // delay(200);        // Pause for 1/10 second

      // Then update coordinates of each flake...
      for(f=0; f< NUMFLAKES; f++) {
        icons[f][YPOS] += icons[f][DELTAY];
        // If snowflake is off the bottom of the screen...
        if (icons[f][YPOS] >= display.height()) {
          // Reinitialize to a random position, just off the top
          icons[f][XPOS]   = random(1 - LOGO_WIDTH, display.width());
          icons[f][YPOS]   = -LOGO_HEIGHT;
          icons[f][DELTAY] = random(1, 6);
        }
      }

    }
    else {
      Serial.println("warnning : in else ");
      display.clearDisplay(); // Clear the display buffer
      delay(10);
      // display.display();    
      display.fillRect(0, 0, display.width(), display.height(), SSD1306_BLACK);
      delay(10);
      display.display();    
    }

  }
  else if(finite_state == 2) {
    Serial.println("state 2 ");
    btn_flag = false;
    current_time_led = millis();
    if (current_time_led >= prev_time_led + 1){
      if (increasing == true) {
        brightness++;
        if (brightness >= 230) {
          increasing = false;
        }
        
      }
      else {
        brightness--;
        if (brightness <= 0) {
          increasing = true;
        }
      }

      analogWrite(led,brightness);
      if (btn_flag == true) {
        return;
      }
      prev_time_led = millis();

    }
    
    current_mode_change_time = millis();
    if ((current_mode_change_time - prev_mode_change_time) <= 1500) {
        display.clearDisplay(); // Clear the display buffer
        display.display();
        delay(1);
        
        display.clearDisplay(); // Clear the display buffer
        display.setTextSize(2);             // Normal 1:1 pixel scale
        display.setTextColor(SSD1306_WHITE);        // Draw white text
        display.setCursor(0,0);             // Start at top-left corner
        display.println(F("State2"));
        display.setTextSize(2);             // Normal 1:1 pixel scale
        display.setTextColor(SSD1306_WHITE);        // Draw white text
        display.setCursor(0,16);             // Start at top-left corner
        display.println(F("Fliker"));
        display.setCursor(0,32);             // Start at top-left corner
        display.println(F("FAST"));
      
      
        display.setTextColor(SSD1306_WHITE);
        
        display.display();
        char_flag = true;
        delay(1500);
    }
    else if (oled_flag == true) {
      display.clearDisplay(); // Clear the display buffer

      // Draw each snowflake:
      for(f=0; f< NUMFLAKES; f++) {
        display.drawBitmap(icons[f][XPOS], icons[f][YPOS], logo_bmp, LOGO_WIDTH, LOGO_HEIGHT, SSD1306_WHITE);
      }

      display.display(); // Show the display buffer on the screen
      // delay(200);        // Pause for 1/10 second

      // Then update coordinates of each flake...
      for(f=0; f< NUMFLAKES; f++) {
        icons[f][YPOS] += icons[f][DELTAY];
        // If snowflake is off the bottom of the screen...
        if (icons[f][YPOS] >= display.height()) {
          // Reinitialize to a random position, just off the top
          icons[f][XPOS]   = random(1 - LOGO_WIDTH, display.width());
          icons[f][YPOS]   = -LOGO_HEIGHT;
          icons[f][DELTAY] = random(1, 6);
        }
      }

    }
    else {
      display.clearDisplay(); // Clear the display buffer
      delay(10);
      // display.display();    
      display.fillRect(0, 0, display.width(), display.height(), SSD1306_BLACK);
      delay(10);
      display.display();    
    }

  }
  else if(finite_state == 3) {
    Serial.println("state 3 ");
    btn_flag = false;
    current_time_led = millis();
    if (current_time_led >= prev_time_led + 5){
      if (increasing == true) {
        brightness++;
        if (brightness >= 230) {
          increasing = false;
        }
        
      }
      else {
        brightness--;
        if (brightness <= 0) {
          increasing = true;
        }
      }

      analogWrite(led,brightness);
      if (btn_flag == true) {
        return;
      }
      prev_time_led = millis();

    }
    
    current_mode_change_time = millis();
    if ((current_mode_change_time - prev_mode_change_time) <= 1500) {
        display.clearDisplay(); // Clear the display buffer
        display.display();
        delay(1);
        
        display.clearDisplay(); // Clear the display buffer
        display.setTextSize(2);             // Normal 1:1 pixel scale
        display.setTextColor(SSD1306_WHITE);        // Draw white text
        display.setCursor(0,0);             // Start at top-left corner
        display.println(F("State3"));
        display.setTextSize(2);             // Normal 1:1 pixel scale
        display.setTextColor(SSD1306_WHITE);        // Draw white text
        display.setCursor(0,16);             // Start at top-left corner
        display.println(F("Fliker"));
        display.setCursor(0,32);             // Start at top-left corner
        display.println(F("SLOW"));
      
      
        display.setTextColor(SSD1306_WHITE);
        
        display.display();
        char_flag = true;
        delay(1500);
    }
    else if (oled_flag == true) {
      display.clearDisplay(); // Clear the display buffer

      // Draw each snowflake:
      for(f=0; f< NUMFLAKES; f++) {
        display.drawBitmap(icons[f][XPOS], icons[f][YPOS], logo_bmp, LOGO_WIDTH, LOGO_HEIGHT, SSD1306_WHITE);
      }

      display.display(); // Show the display buffer on the screen
      // delay(200);        // Pause for 1/10 second

      // Then update coordinates of each flake...
      for(f=0; f< NUMFLAKES; f++) {
        icons[f][YPOS] += icons[f][DELTAY];
        // If snowflake is off the bottom of the screen...
        if (icons[f][YPOS] >= display.height()) {
          // Reinitialize to a random position, just off the top
          icons[f][XPOS]   = random(1 - LOGO_WIDTH, display.width());
          icons[f][YPOS]   = -LOGO_HEIGHT;
          icons[f][DELTAY] = random(1, 6);
        }
      }

    }
    else {
      display.clearDisplay(); // Clear the display buffer
      delay(10);
      // display.display();    
      display.fillRect(0, 0, display.width(), display.height(), SSD1306_BLACK);
      delay(10);
      display.display();    
    }

  }
  else if(finite_state == 4) {
    Serial.println("state 4 ");
    btn_flag = false;
    current_time_led = millis();
    if (current_time_led >= prev_time_led + 7){
      if (increasing == true) {
        brightness++;
        if (brightness >= 200) {
          increasing = false;
        }
        
      }
      else {
        brightness--;
        if (brightness <= 25) {
          increasing = true;
        }
      }

      analogWrite(led,brightness);
      if (btn_flag == true) {
        return;
      }
      prev_time_led = millis();
    }
    current_mode_change_time = millis();
 if ((current_mode_change_time - prev_mode_change_time) <= 1500) {
        display.clearDisplay(); // Clear the display buffer
        display.display();
        delay(1);
        
        display.clearDisplay(); // Clear the display buffer
        display.setTextSize(2);             // Normal 1:1 pixel scale
        display.setTextColor(SSD1306_WHITE);        // Draw white text
        display.setCursor(0,0);             // Start at top-left corner
        display.println(F("State4"));
        display.setTextSize(2);             // Normal 1:1 pixel scale
        display.setTextColor(SSD1306_WHITE);        // Draw white text
        display.setCursor(0,16);             // Start at top-left corner
        display.println(F("Fliker"));
        display.setCursor(0,32);             // Start at top-left corner
        display.println(F("SUPER SLOW"));
      
      
        display.setTextColor(SSD1306_WHITE);
        
        display.display();
        char_flag = true;
        delay(1500);
    }
 else if (oled_flag == true) {
      display.clearDisplay(); // Clear the display buffer

      // Draw each snowflake:
      for(f=0; f< NUMFLAKES; f++) {
        display.drawBitmap(icons[f][XPOS], icons[f][YPOS], logo_bmp, LOGO_WIDTH, LOGO_HEIGHT, SSD1306_WHITE);
      }

      display.display(); // Show the display buffer on the screen
      // delay(200);        // Pause for 1/10 second

      // Then update coordinates of each flake...
      for(f=0; f< NUMFLAKES; f++) {
        icons[f][YPOS] += icons[f][DELTAY];
        // If snowflake is off the bottom of the screen...
        if (icons[f][YPOS] >= display.height()) {
          // Reinitialize to a random position, just off the top
          icons[f][XPOS]   = random(1 - LOGO_WIDTH, display.width());
          icons[f][YPOS]   = -LOGO_HEIGHT;
          icons[f][DELTAY] = random(1, 6);
        }
      }

    }
    else {
      display.clearDisplay(); // Clear the display buffer
      delay(10);
      // display.display();    
      display.fillRect(0, 0, display.width(), display.height(), SSD1306_BLACK);
      delay(10);
      display.display();    
    }




  }
  else {
    Serial.println("incorrect state");

  }
  
   
  

}

void interrupt_mode() {
  current_time = millis();
  if (digitalRead(mode_changer) > 0 ){
    if(current_time > (prev_time + 200)) {
      btn_flag = true;
      if( finite_state >= 4) {
        finite_state = 0;
      }
      else {
        finite_state++;
      }
      prev_time = current_time;
      prev_mode_change_time = millis();
    }
  }
}

void interrupt_oled() {
  Serial.println("interrupted");
  current_time = millis();
  if (digitalRead(oled_changer) > 0 ){
    if(current_time > (prev_time + 200)) {
      if (oled_flag == true) {
        oled_flag = false;
      }
      else {
        Serial.println("clear!");
        
        for(f=0; f< NUMFLAKES; f++) {
          icons[f][XPOS]   = random(1 - LOGO_WIDTH, display.width());
          icons[f][YPOS]   = -LOGO_HEIGHT;
          icons[f][DELTAY] = random(1, 6);
          
        }
        // display.display();
        oled_flag = true;
      }
      prev_time = current_time;
    }
  }

}


void testanimate_step(const uint8_t *bitmap, uint8_t w, uint8_t h) {
  static int8_t icons[NUMFLAKES][3]; // Snowflake data (persistent across calls)
  static bool initialized = false;  // Ensure icons are initialized only once

  // Initialize snowflakes only once
  if (!initialized) {
    for (int f = 0; f < NUMFLAKES; f++) {
      icons[f][XPOS] = random(1 - LOGO_WIDTH, display.width());
      icons[f][YPOS] = -LOGO_HEIGHT;
      icons[f][DELTAY] = random(1, 6);
    }
    initialized = true;
  }

  display.clearDisplay(); // Clear the display buffer

  // Draw each snowflake:
  for (int f = 0; f < NUMFLAKES; f++) {
    display.drawBitmap(icons[f][XPOS], icons[f][YPOS], bitmap, w, h, SSD1306_WHITE);
  }
  display.display(); // Show the display buffer on the screen

  // Update coordinates of each flake
  for (int f = 0; f < NUMFLAKES; f++) {
    icons[f][YPOS] += icons[f][DELTAY];
    if (icons[f][YPOS] >= display.height()) {
      icons[f][XPOS] = random(1 - LOGO_WIDTH, display.width());
      icons[f][YPOS] = -LOGO_HEIGHT;
      icons[f][DELTAY] = random(1, 6);
    }
  }
}

 

 

 

 

이렇게 조금 긴 코드가 완성되었다.

사실 led만 사용하면 이렇게 길어질 이유가 없지만, oled를 사용해 조금 길어졌다.

대충 동작을 설명하면, 버튼을 두 개 만들어서 하나는 oled의 피로도를 아끼기 위해 켜고 끄는 버튼을 만들었고, 하나는 led strip의 state를 변경하는 버튼을 만들었다. oled의 피로도를 생각하게 된건, 사실 아래와 같이 별들이 무수히 떨어지는 움짤을 넣었기 때문인데, 이게 예제에 있었는데 크리스마스와 좀 어울리는 듯 하여 그대로 가져오게 되었다.

(좌) 별이 무수히 떨어지는 idle 상태, (우) state 변경시 잠깐 뜨는 안내문구

 

별과 글씨가 잘리는 이유는 단순히 주사율 때문이다.

 

 

2. 회로

사실 이런 프로젝트에서 회로는, 납땜을 해야하기에 기존에 가진 설계 툴로 구상과 테스트를 진행해야 하지만, 이 프로젝트는 회로가 너무 단순해 그냥 그자리에서 즉석으로 해버렸다. 구상부터 납땜 및 테스트까지 네시간 정도 걸렸던 것 같다.

단순히 3.3V를 사용하고 풀다운 저항을 이용해 interrupt를 구현한 버튼 두개와 oled를 하나 장착했다. 위쪽에 보면 풀다운을 위한 1k ohm 저항 두개가 보일것이다.

 

 

 

 

 

 

3. 모델링

다음은 모델링이다. 아무리 내용물이 좋다 하더라도 안예쁘면 말짱 도루묵이다.

적당한 케이스를 만들고, 아두이노를 끼워넣을 수 있도록 아두이노의 원래 나사구명 네개의 수치를 버니어캘리퍼로 측정해 만들었다. 사실 이 모델링을 printing하기 위해서는 본인이 갖고있는 프린터의 출력공차를 알고있어야 하는데, 필자의 경우는 처음 쓰는 프린터를 사용했기 때문에 세 번 정도의 시행착오를 거쳤다. 색상에 따라서 출력공차나 수축률이 바뀌는것도 알고있어야 한다.

이렇게 케이스와 버튼을 모델링 했다.

버튼은 바깥에서 누르거나 잡아당겼을 때 빠지지 않도록 T자형으로 설계했다.

 

4. 3d프린팅

 

생각보다 잘 나왔고, 바로 아두이노가 끼워진 채 고정이 됐다.

 

 

5. 완료

 

 

xrated 글라스는 별로 쓸 일이 없어서 그냥 led strip의 남는 부분을 전부 쑤셔넣었다

이렇게 보니, 윗층의 술장이 잘 안보이지만 그냥 쓸련다..

 

버튼으로 모드 변경하고 oled도 껐다켰다 할 수 있으니 여러 모드를 즐기고, oled의 수명도 고려할 수 있다.

이렇게 보면 약간은 낭만이 있지 않은가

 

 

여담

이공계들은 사실 낭만을 추구할 수 있는데 추구하지 않는다. 그들은 대체로 기능주의적이며, 본질에 대한 이해를 추구하는 사고를 하기 때문에 그들이 추구하는 낭만은 다른 사람들이 생각하는 것과는 조금 다른 곳에 있다. 하지만 이런 낭만들은 본질적으로 추구하는 바가 같으며, 이공계던 예체능이던 서로의 낭만을 존중해주기 바라며, 필자는 각 분야의 낭만들이 융합될때 비로서 완벽한 낭만이 나올 수 있다고 생각한다. 그러한 예시들이 디자인과 공학의 결합체인 제품들이다.

'Projects' 카테고리의 다른 글

[VScode & Git] windows에서 VScode와 git을 연동해보자.  (0) 2025.04.24