RS's Travel & Electronic

버튼 : 상황에 맞는 응용 함수 본문

시퀀스와 아두이노

버튼 : 상황에 맞는 응용 함수

RS 2021. 7. 11. 18:00
SMALL

아두이노에서 흔히 쓰이는 택트 버튼.

푸시버튼의 일종입니다.

위의 기판은 푸시버튼을 이용한 테스트를 해야 될 때를 위해서 미리 만들어놓은 보드입니다.

 

아두이노로 시제품을 만드는 일을 하다 보면, 여러 가지 이유로 제품에 버튼을 한 개밖에 달 수 없는 경우가 있습니다.

그럴 땐 한 개의 버튼으로 여러 동작을 할 수 있게 해 줘야 됩니다.

 

예를 들어 한번 짧게 누르면 A동작을, 1초 이상 누르고 있으면 B 동작을, 2초 이상 누르고 있으면 C동작을, 더블클릭(빠르게 두 번 누르기)을 했을 경우 D동작을.

 

또 버튼을 누르는 순간 동작하기를 원할 때도 있고, 버튼을 누르고 떼는 순간 동작하기를 원하는 경우도 있겠죠.

아니면 버튼을 여러 개 달긴 했지만 각 버튼마다 동작 트리거가 각각 다르게 발생하기를 원하는 경우도 있습니다.

 

그래서 사실, 아두이노를 이용한 시제품 제작을 하면서 꽤 많이 고민한 게 버튼에 관련된 이슈입니다.

누르면 반응을 보여준다.라는 단순한 메커니즘이지만 꽤 생각할게 많더군요.

 

아두이노에서 푸시버튼, 혹은 누름 버튼을 사용할 때 쓸 수 있는 함수를 정의해 봤습니다.

참고로, 전부 버튼을 pinMode에서 INPUT_PULLUP으로 설정했을 때 동작합니다.

안 그러면 이상하게 동작하거나 아두이노가 폭주합니다 ㅋㅋ

 

 

 

1. 푸시 타임 동작 버튼

#define     THISTIME       10
#define     BUTTON         2

/* Push Time 작동형 */
void btPush_1()
{
  static boolean b1Stat = false;
  static boolean laB1Stat = true;
  static unsigned long laB1Time = 0;
  
  boolean readingStat = digitalRead(BUTTON);
  if (readingStat != laB1Stat) laB1Time = millis(); 
  if ( (millis() - laB1Time) > THISTIME ) {
    if (readingStat != b1Stat) {
      b1Stat = readingStat;
      if (b1Stat == false) {  
        /* 내용 */
      }
    }
  }
  laB1Stat = readingStat;
}

가장 일반적인 푸시버튼 동작 코드입니다.

최초의 버튼 눌림을 감지한 시간으로부터 채터링 현상 방지 타임(디파인으로 선언한 THISTIME, 5~10 정도 주면 됨) 동안은 무시하고 있다가 채터링이 끝나고 나서도 버튼이 여전히 눌림 상태라면 필요한 동작을 실행하는 코드 되겠습니다.

 

/* 내용 */ <- 이 부분에 동작하기를 원하시는 코드를 삽입하시면 됩니다.

 

 

 

2. 릴리즈 타임 동작 버튼

#define     THISTIME       10
#define     BUTTON         2

/* Release Time 작동형 */
void btRelease_1()
{
  static boolean b1Stat = true;
  static boolean laB1Stat = true;
  static unsigned long laB1Time = 0;
  
  boolean readingStat = digitalRead(BUTTON);
  if (readingStat != laB1Stat) laB1Time = millis(); 
  if ( (millis() - laB1Time) > THISTIME ) {
    if (readingStat != b1Stat) {
      b1Stat = readingStat;
      if (b1Stat == true) {  
        /* 내용 */       
      }
    }
  }
  laB1Stat = readingStat;
}

이번엔 사용자가 버튼을 누르는 순간이 아니라 눌렀다 떼었을 때 동작하는 코드입니다.

푸시 타임 버튼 함수와 마찬가지로 채터링을 회피한 후에 이번엔 버튼이 떼였을 때 함수가 동작할 수 있도록 만들어졌습니다. 사실 달라진 것은 마지막 if문에서 b1 Stat을 false와 비교하는 게 아니라 true로 비교한 것뿐이지만요.

 

여기서도 /* 내용 */ 안에 동작시키고 싶은 내용을 넣으시면 됩니다.

 

 

 

3. 시간대별 릴리즈 타임 동작 버튼 코드

#define     SEC1           1000
#define     SEC2           2000
#define     THISTIME       10
#define     BUTTON         2

/* Time 작동형 - Release 
 *  버튼에서 손을 떼는 순간 작동한다. 타이밍을 여러개로 늘릴수 있음. */
void btPressTime_1 ()
{
  static boolean prev = HIGH;
  static unsigned long last, pressed;
  boolean readingStat;

  if ( (millis() - last ) >= THISTIME) {
    readingStat = digitalRead(BUTTON);
    if ((HIGH==prev)&&(LOW==readingStat)) {
      pressed = millis();
      prev = LOW;
    } 
    else if ((LOW==prev)&&(HIGH==readingStat)) {
      prev = HIGH;
      if ((millis()-pressed) > SEC2) { // 시간지정
        /* 2초지속 내용 */
        return;
      } 
      if ((millis()-pressed) > SEC1) { // 시간지정
        /* 1초지속 내용 */
        return;
      } 
      else {
        /* 숏타임 내용 */
        return;
      }
    }
    last = millis();
  }
}

이 함수가 1개의 버튼으로 여러 동작을 수행할 수 있는 함수입니다.

사용자가 버튼을 누르고 나서 떼기까지의 시간이 얼마냐에 따라서 어떤 동작을 할지 결정짓습니다.

이 예시에서는 보통으로 버튼을 눌렀다 뗄 경우, 버튼을 눌렀다가 떼는데 1초, 버튼을 눌렀다가 떼는데 2초, 의 세 가지 경우에 따라 각각 다른 내용을 실행할 수 있습니다.

 

숏타임 내용 <- 여기에 보통 버튼 누름의 동작 내용을, 1초 지속 내용과 2초 지속 내용에 각각 필요한 코드를 넣으시면 되겠습니다.

 

버튼의 지속시간을 변경하시려면 define으로 지정된 SEC1과 SEC2를 수정해주시면 됩니다.

물론 경우의 수를 더 늘릴 수도 있습니다.

 

 

 

4. 더블클릭 (문제 있음...)

#define     BUTTON         2
#define     THISTIME       10
#define     DC_THISTIME    400

void btDoubleClick_1 ()
{
  static boolean b1Stat = true;
  static boolean laB1Stat = true;
  static uint8_t b1ClickCount = 0;
  static unsigned long firstB1Time = 0;
  static unsigned long secondB1Time = 0;


  if ( (b1ClickCount == 1) && (millis() - secondB1Time) > DC_THISTIME ) {
    /* 원클릭 내용 */
  }
  else if ( (b1ClickCount == 2) ) {
    /* 투클릭 내용 */
  }

  
  boolean readingStat = digitalRead(BUTTON);
  if (readingStat != laB1Stat) firstB1Time = millis(); 
  if ( (millis() - firstB1Time) > THISTIME ) {
    if (readingStat != b1Stat) {
      b1Stat = readingStat;
      if (b1Stat == true) {  
        b1ClickCount++;
        if (b1ClickCount == 1) secondB1Time = millis();
      }
    }
  }
  laB1Stat = readingStat;
}

사실 가장 구현해보고 싶었던 더블클릭(두 번 누름) 함수입니다.

버튼을 한 번만 눌렀을 때와 두번연속 눌렀을때 각각 다른 동작을 하도록 구현하려 했습니다.

 

먼저 알아두실 것이 여기서 구현하고자 하는 더블클릭은

 

이거의 더블클릭이 아니란 겁니다.

 

우리가 마우스로 윈도에 더블클릭을 하는 경우, 원클릭을 포함한 형태가 됩니다.

뭔 소리인고 하면.. 아이콘 위에 커서를 놓고 더블클릭을 하면 먼저 아이콘이 선택되는 과정을 거쳐서 일정 시간 내에 한번더 클릭이 더 들어와서 그 아이콘을 실행하는 시퀀스를 진행하게 되죠. 아이콘이 아니라도 과정은 같습니다. 먼저 원클릭에 대한 시퀀스를 거치고, 추가로 일정시간내에 클릭이 들어오면 더블클릭에 대한 시퀀스도 실행하죠.

여기서 구현하는 더블클릭은 원클릭을 포함하지 않습니다. 들어온 신호가 원클릭이라면 A액션을, 더블클릭이라면 A액션을 하지 않고 B액션을 하는 식으로 두 가지 서로 다른 의사를 사용자가 표현할 수 있도록 하려는 겁니다.

 

사용자가 한번 누름을 위해서 버튼을 눌렀어도 첫 버튼 누름의 타이밍에서는 아두이노가 그 의사를 파악할 수 없습니다.

첫 누름이 들어오고 나서 일정시간 동안 두 번째 누름이 들어오는지, 즉 더블클릭으로 인정될 수 있는 두번째 누름이 오는지를 감시하고, 그 시간동안 두번째 누름이 없다고 알게 된 후에야 비로소 "아 이놈이 원클릭하려고 했구나" 하고 아두이노가 한번누름에 해당하는 코드를 실행하겠죠.

 

두번째 누름이 들어오는지 감시하는, 딜레이 타임 때문에 코드에 좀 문제가 생겼습니다.

 

버튼을 한번 누르는 것은 잘 인식하는데, 두 번 누름을 잘 인식못합니다. 너무 빨리 두번누름을 해도 인식 못하고요.

시제품 제작을 위한 아두이노 프로그래밍을 할 때는 여러 가지 외부기기를 제어하는 코드가 들어가야 됩니다. 개중에는 아두이노 자체에 꽤 심각한 딜레이를 발생시키는 코드도 있죠. 예를 들어 온습도 센서 DHT계열의 라이브러리.

이런 코드들이 추가될 경우 더블클릭이 거의 인식되지 않습니다. 

 

이 부분은 좀 더 연구가 필요할 거 같습니다. 브레드보드에 버튼과 LED 하나씩 달아놓고 실험할 때는 그럭저럭 잘 될 겁니다만, 실전에 사용할 수는 없는 코드입니다.

 

 

 

5. 지속 누름 반응형

#define     BUTTON         2
#define     THISTIME       10
#define     CONTINUESEC    500
#define     CONTINUECYCLE  100 

int val1 = 0;

/* Push Time 지속형 */
void btPush_2()
{
  static boolean b1Stat = false;
  static boolean laB1Stat = true;
  static unsigned long laB1Time = 0;

  static boolean nowPushed = false;
  static unsigned long pushStartTime = 0;
  static unsigned long continueStartTime = 0;
  
  boolean readingStat = digitalRead(BUTTON); // 버튼상태 스캔
  
  if (nowPushed) {         // 플래그확인
    if (readingStat) nowPushed = false; // 버튼이 떼인상태라면 플래그OFF
  
    if ( (nowPushed) && ( (millis() - pushStartTime) > CONTINUESEC ) ) { 
    // 기준밀리초 만큼 버튼이 눌림상태로 유지되었는지
      if ( (millis() - continueStartTime) > CONTINUECYCLE ){ 
      // 눌림상태동안 반복실행될 동작의 사이클을 결정
        /* 액션2 */
        val1++;
        Serial.print ("val1 : ");
        Serial.println (val1);
        continueStartTime = millis();
      }
    }
  }
  
  if (readingStat != laB1Stat) laB1Time = millis();  // 채터링현상 점프
  if ( (millis() - laB1Time) > THISTIME ) {
    if (readingStat != b1Stat) {
      b1Stat = readingStat;
      if (b1Stat == false) {  
        /* 액션1 */
        nowPushed = true; 
        // 채터링을 넘겨서 버튼이 눌린상태라고 확인되면 플래그 ON
        pushStartTime = millis(); 
        // 버튼눌림이 확정되었을 때의 시간을 저장
        val1++;
        Serial.print ("val1 : ");
        Serial.println (val1);
      }
    }
  }
  laB1Stat = readingStat;
}

1번 푸시 타임 동작 버튼에 지속 반응을 추가한 코드입니다.

버튼을 누르면 val1의 값이 1 증가하고 시리얼 모니터에 출력됩니다.

누른 상태로 CONTINUESEC 밀리초만큼 유지하면 계속해서 CUNTINUECYCLE 밀리 초마다 1씩 증가하며 시리얼 모니터에 출력됩니다.

최초 버튼 누름 시 동작이 액션 1에 계속 누르고 있을 때의 동작이 액션 2에 들어갑니다.

 

참고로... delay를 쓰면 프로그램 라인은 줄어들 겁니다.

하지만 실전에서는 절대로 써먹을 수 없는 코드가 되죠. delay는 지정한 시간 동안 아두이노를 완전히 재워버리니까요. 원스 레드로 동작하는 아두이노에서 delay는 사용할 곳이 한정되어 있습니다. 실험용 코드, 혹은 setup에서 사용하는 정도입니다.

 

하나 더 얘기하자면 함수 내에 굳이 static 변수를 선언한 것은 큰 의미는 없고요, 만약 버튼을 여러 개 달아야 할 경우, 버튼수만큼 버튼에 관련된 변수들이 전역으로 선언되는 게 복잡해서 이렇게 한 겁니다.

버튼 함수는 한번 구현 해 놓으면 눌렸을 때 어떤 동작을 할지 정하는 부분만 수정할 뿐 그 외에 것은 손댈 일이 거의 없습니다. 변수의 이름을 바꿔본다거나 값을 바꾼다거나 하는 일도 없죠. 그런데도 프로그램 최상단에 다른 변수들과 함께 선언문이 늘어서 있으면 영 귀찮습니다 ㅋ

 

여기에 추가로 더블클릭을 구현하게 되면 마우스 버튼과 유사한 역할을 할 수 있을 거 같은데, 이 부분이 좀 어렵네요.

LIST
Comments