2018년 10월 23일 화요일

360도 회전 가능한 로터리 인코더를 사용하자.

1. 세월은 변했지만...


 아날로그 세상에서 디지털 세상으로 세월이 변했지만, 아날로그에 대한 향수는 지울수 없다. 감성과 사용의 편리성을 고려하면 로터리 인코더는 아주 오랜 세월을 나와 함께 할것 같다.
360도 회전 가능한 로터리 인코더

이 모델은 연결핀이 5개 이며, 중심부는 버튼 스위치 기능이 있다.


2. 연결은 해보았지만...

 연결이 어려운 것도 아니고, 저항 두개와 점퍼케이블만 있으면 가능하니, 바로 연결해 보았다.  아래의 그림처럼 연결하고 각 핀의 이름은 인터넷을 뒤적 뒤적하여 매칭했다.


신호를 좀더 제대로 처리하고 싶다면, 아래와 같이 연결하면 될듯 하다.
(난 저항만 쓰고 나머지는  S/W로 해결)



각핀을 Wemos D1 mini에 아래와 같이 연결하였다.
    Wemos             Encoder
    D1 ------------------ CLK
    D2 ------------------ DT
    D3 ------------------ SW
    3.3 ------------------ +
    GND ---------------  GND

허허.. 연결을 하고 코드를 실행하니, 난리도 아니였다.  버튼을 한번 눌렀다가 떼었는데 2번 눌렸다 뗀걸로 신호가 잡히고, 시계 방향으로 돌리고 있는데 반시계 방향 신호가 잡히는 둥 사용하기 껄끄러운 상태였다.


3. S/W 방식으로 보완을...

 아마도 내가 구입한 인코더가 워낙 싼 모델 인듯 하여 소프트웨어 방식으로 보정을 했다. 0.01초 이내에 버튼 신호가 2회 연속 들어오면 뒤의 버튼 신호를 무시하는 방식이다. 회전신호 역시 0.01초 이내의 신호는 무시하는 방식이다.  결론은 아주 성공적이었다. 물론 완벽한 신호를 원한다면, 더 좋은 모듈을 사서 사용하는 것을 권장한다.


4. 사용 방법 및 소스코드는...

 사용 방법은 어려울 것도 없다. 그냥 헤더 파일을 인클루드 하고 신호핀 3개만 잘 정의해 주면 된다. 아래와 같이 사용한다.  ( loop 함수에 feed() 호출 필수)

/*------------------------------------------------------------------
    360 회전 가능한 로터리 인코더 사용 예제
------------------------------------------------------------------*/
#include "Rotary360Potentiometer.h"

// 신호를 받기 위하여 D1, D2, D3  핀을 사용한다
Rotary360Potentiometer pot(D1, D2, D3);

void setup() {
  Serial.begin (115200);
}

void loop() {
  switch (pot.feed()) {
    case POTNET_NEUTRAL:
      break;
    case POTNET_CW:
      Serial.println("Potentiometer CW");
      break;
    case POTNET_CCW:
      Serial.println("Potentiometer CCW");
      break;
    case POTNET_BTN_DN:
      Serial.println("Potentiometer BTN DN");
      break;
    case POTNET_BTN_UP:
      Serial.println("Potentiometer UP");
      break;
  }
}


Rotary360Potentiometer.h  file

/*------------------------------------------------------------------
   BSD License
   Copyright (c) 2018 terminal0070@gmail.com

  360 회전 가능한 로터리 포텐셔미터를 위한 클래스
  5개의 핀을 가지는 포텐셔 미터용이다.
  아래의 외형을 가지며, 각핀의 명칭은 아래와 같다.
           +--------------+
 A (CLK) --|              |--   SW
 C (GND) --|     (  )     |
 B   (DT)--|              |--  VCC
           +--------------+           
  ------------------------------------------------------------------*/
#ifndef POTENTIOMETER_360_H
#define POTENTIOMETER_360_H

enum {POTNET_NEUTRAL, POTNET_CW, POTNET_CCW, POTNET_BTN_DN, POTNET_BTN_UP};
class Rotary360Potentiometer
{
public:
    Rotary360Potentiometer(uint8_t clk_pin, uint8_t dt_pin, uint8_t sw_pin = 255);
  uint8_t feed(void);
private:
  uint8_t m_dClkPin, m_dDtPin;   // pin A, B 정의
  uint8_t m_dSwPin = 255;        // 버튼핀을 사용하는 경우와 사용하지 않는 경우가 있음..
  int8_t m_dPrevClkPinVal;       // CLK(A) Pin 최종 상태
  int8_t m_dPrevBtnStatus;       // 버튼의 최종상태
  // 버튼을 한번 누르지만 연속으로 누르는 것처럼 되는 것을 막기 위해서..
  uint32_t m_dPrevBtnUpTime = 10;
  // 아래 두개 변수는 로터리를 빠르게 돌리면 가끔씩 역방향으로 돌아 갔다고 센싱하길래
  // 소프트웨어 적으로 보정하는데 사용함.
  uint32_t m_dPrevStatusTime = 10; // 마지막으로 회전을 확인한 시간
};

#endif // POTENTIOMETER_360_H 



Rotary360Potentiometer.cpp file
/*------------------------------------------------------------------
   BSD License
   Copyright (c) 2018 terminal0070@gmail.com
------------------------------------------------------------------*/
#include <Arduino.h>
#include "Rotary360Potentiometer.h"

Rotary360Potentiometer::Rotary360Potentiometer(uint8_t clk_pin, uint8_t dt_pin, uint8_t sw_pin) {
  m_dClkPin = clk_pin;
  m_dDtPin = dt_pin;
  m_dSwPin = sw_pin;
  pinMode(m_dClkPin, INPUT);
  pinMode(m_dDtPin, INPUT);
  m_dPrevClkPinVal = digitalRead(m_dClkPin);
  if (m_dSwPin != 255) { // Switch 핀을 사용한다면
    pinMode(m_dSwPin, INPUT);
    m_dPrevBtnStatus = digitalRead(m_dSwPin);
  }
}

uint8_t Rotary360Potentiometer::feed(void) {
  int8_t status = POTNET_NEUTRAL;
  uint32_t now = millis();

  // 로터리 상태 체크
  int nowClkVal = digitalRead(m_dClkPin);
  if (nowClkVal != m_dPrevClkPinVal) { // 돌리고 있는 상황이면
    // CLK(A)핀이 먼저 바뀌엇다. -> 시계 방향으로 회전중
    if (digitalRead(m_dDtPin) != nowClkVal) {
      status = POTNET_CW;
    } else {// DT(B) 핀이 먼저 바뀌었다.. -> 반시계 방향으로 회전중
      status = POTNET_CCW;
    }
    m_dPrevClkPinVal = nowClkVal;
   
    // 로터리 신호를 소프트웨어적으로 보정
    // 0.01 이내의 회전 신호가 다시 잡히면 무시한다.
    // 빠른시간내에 회전 신호가 들어온다는 것은 대부분
    // 잘못된 신호이다.
    if (now - m_dPrevStatusTime > 10)  {
      m_dPrevStatusTime = now;
    } else {
      status = POTNET_NEUTRAL;
    }
  }

  // 버튼 처리
  // 코드상 나중이지만, 결론은 버튼 상태가 로터리 보다 우선되는 형태이다
  if (m_dSwPin != 255) {
    int nowBtn = digitalRead(m_dSwPin);
    if (m_dPrevBtnStatus != nowBtn) {
      // 역시 0.01 이내에 버튼 신호가 연속으로 잡히면.. 결론은 무시한다.
      if (now - m_dPrevBtnUpTime > 10) {
        if (nowBtn) {
          status = POTNET_BTN_UP;
        } else {
          status = POTNET_BTN_DN;
        }
        m_dPrevBtnStatus = nowBtn;
        m_dPrevBtnUpTime = now;
      }
    }
  }

  return status;
}



3단 6핀 스위치로 DC 모터의 회전 방향을 바꾸어 보자

1. 필요는 연구의 어머니 항상 느끼는 부분이다. 필요하지 않으면 연구하지 않으며, 필요하면 연구한다. DC 모터를 조건에 따라서 정방향 또는 역방향으로 회전시켜야 하는 필요가 생겼다. 처음에는 MCU 및 Relay Switch를 이용하는 방법을 생각...