Rotary Encoder로 속도 제어하기 (with ESP32, Raspberry Pi)

GAON PARK·2023년 11월 23일
0
post-custom-banner

Rotary Encoder

  • 회전하는 축의 위치를 측정하기 위해 사용되는 기기이다.
  • 회전 속도, 방향, 각도를 축정하는데 도움을 준다.

회로 연결

MPU6050, 직진/후진, 좌회전/우회전 버튼을 연결했던 ESP32는 브레드보드의 왼쪽편에 그대로 두고, 오른편에 핀이 겹치지 않도록 ESP32를 하나 더 달았다. (ESP32 하나로도 충분히 할 수 있을 것 같지만 두 개를 쓰면 센서 한두개 더 연결할 수 있을 것 같아서)

rotary encoderesp32
gndgnd
+3.3v
swgpio36
dtgpio39
clkgpio34

ESP32 코드

참고 블로그 : https://blog.naver.com/PostView.nhn?blogId=gbtec&logNo=221336130885&from=search&redirect=Log&widgetTypeCall=true&directAccess=false

  • 새로운 ESP32이기 때문에 mqtt client설정, thread 헤더 include 등 기본적인 설정을 똑같이 해준다. 다만 mqtt client id, name은 다르게 설정해주어야 한다. (안 그러면 브로커 서버 내에서 클라이언트가 하나로 인식되어 한 쪽 esp32만 작동한다;)
  • ROTARY_ENCODER_XXX : 로타리 엔코더 연결 핀을 define 한다.
  • 속도는 모터와 관련되어 있으므로 command topic으로 보낼 것이다.
  • LAST_ENCODED는 값이 바뀌지 않는 한, 값을 한 번만 읽고 mqtt 송신 한 번만 보내도록 하기 위해 만든 bool 변수로 volatile 키워드를 추가해 과하게 똑똑한 컴파일러의 방해(!)를 차단했다.
  • SPEED는 실제 속도 레벨로 치환되는 값이다. (1~9이지만 왜인지 조금 돌려도 값이 1~2씩 튀어서 라즈베리파이쪽 코드에서 범위로 idx를 메겼다)
  • rotary_check() : 커스텀 객체가 아닌, 기본 Thread 객체를 만들어서 interval 마다 주기적으로 실행할 함수를 정의했다.
    - 시계방향 : CLK 신호가 High->Low일 때 DT 신호가 High
    - 반시계방향 : CLK 신호가 High->Low일 때 DT 신호가 Low
  • 계속해서 값을 읽어와야 하기 때문에 이전과 마찬가지로 thread로 구현했다.
#define ROTARY_ENCODER_SW 36
#define ROTARY_ENCODER_DATA 39
#define ROTARY_ENCODER_CLK 34

// ... EspMQTTClient 코드
// ... MQTT 송신용 tx()
// ... 

// 한 번만 보내기 위한 로직 변수
volatile int LAST_ENCODED = 0, SPEED = 1;

// mqtt 보낼 payload
char rotary_command_base[10] = "speed=";

// 스레드가 설정된 interval에 맞춰 주기적으로 실행할 함수
void rotary_check(void) {
  int clk = digitalRead(ROTARY_ENCODER_CLK);
  int dt = digitalRead(ROTARY_ENCODER_DATA);
  int encoded = (clk << 1) | dt;
  int sum = (LAST_ENCODED << 2) | encoded;

  if (sum == 0b1101 || sum == 0b1011) {  // left
    if (SPEED > 1) {                                         // min SPEED = 1
      // Serial.println("rotary change");
      SPEED--;
      rotary_command_base[6] = SPEED + '0';
      tx(CMD_TOPIC, rotary_command_base);
    }
  }
  if (sum == 0b1110 || sum == 0b1000) {  // right
    if (SPEED < 9) {                     // max SPEED = 9
      // Serial.println("rotary change");
      SPEED++;
      rotary_command_base[6] = SPEED + '0';
      tx(CMD_TOPIC, rotary_command_base);
    }
  }
  LAST_ENCODED = encoded;
}

// create thread
Thread rotary_th = Thread();

// ... thread controller 관련 코드
void setup(void) {
  // ... serial monitor begin
  // ... mqtt client enable 

  // 로타리 엔코더 핀 설정
  pinMode(ROTARY_ENCODER_SW, INPUT);  
  pinMode(ROTARY_ENCODER_DATA, INPUT); 
  pinMode(ROTARY_ENCODER_CLK, INPUT);

  // callback thread func
  // 로타리 엔코더 스레드 callback func
  rotary_th.onRun(rotary_check); 
  rotary_th.setInterval(10);
  
  // rotary thread add to thread controller
  controller.add(&rotary_th);
}

// ... callback for the Timer
// ... onConnectionEstablished()

void loop() {  
  controller.run();  
  client.loop();  
}

라즈베리파이 코드

기존 cmd_thread.py 에 내용을 추가한다.

class CmdThread(threading.Thread):
	# ... broker_address, speed_idx, speed 객체 변수 
    # ... is_lr_all_false()

    def on_command(self, client, userdata, message):
		# ... cmd parsing
		# ... if go, back, left_m/M, right_m/M, mid, stop 코드
		
		# speed=xx 로 값이 오기 때문에 in 키워드로 조건 걸기
        elif "speed" in cmd:
            idx = int(cmd.split("=")[1]) # 값 파싱
            # 범위로 인덱싱
            if idx <= 2: 
                self.speed_idx = 0
            elif idx <= 4:
                self.speed_idx = 1
            elif idx <= 6:
                self.speed_idx = 2
            else:
                self.speed_idx = 3
            self.speed_changed()
	
	# 속도 값이 변경되었을 때 실행할 함수
    def speed_changed(self):
        if IS_FRONT: # 지금 직진 중이면 PWM 재설정 후 자동 직진
            self.go()
        elif IS_BACK: # 지금 후진 중이면 PWM 재설정 후 자동 후진
            self.back()
	
	# ... init(), run()
	# ... def 전, 좌회, 우회, 후, 중앙, 멈춤 메서드
post-custom-banner

0개의 댓글