[라즈베리파이 + DC모터 + PYTHON] 2. 라즈베리파이로 엔코더 읽기

알도 2017-11-15 (수) 17:23 6년전 5966  

라즈베리파이 DC모터 PYTHON 엔코더 홀센서


안녕하세요.

[라즈베리파이 + DC모터 + PYTHON] 1. 라즈베리파이와 MD10C로 DC모터 제어하기에 이어서 하는 강좌입니다.


지난번에 아두이노, DC모터 드라이버, 엔코더를 이용해서 위치제어하는 방법을 알아봤는데요.

[아두이노 + DC모터]1. L298로 DC모터 제어하기

[아두이노 + DC모터]2. DC모터 엔코더 사용하기
[아두이노 + DC모터]3. 엔코더를 이용한 정밀한 모터 위치 제어


이번에는 아두이노를 라즈베리파이로 바꿔서 해보겠습니다.

엔코더에 관한 이론적인 부분은 위에서 두번째 강좌를 참고하시길 바랍니다.


지난 강좌에서 라즈베리파이는 아두이노에 비해 하드웨어의 마이크로컨트롤러로써의 기능이 약하다고 말씀드린 바 있습니다.

그래서 PWM을 소프트웨어적으로 구현했는데요. 오늘 엔코더를 측정할 인터럽트 기능 역시 소프트웨어적으로 구현되기 때문에 어떤 GPIO핀도 사용할 수 있다는 장점이 있으나 하드웨어도 구현된 아두이노 인터럽트에 비하면 다소 늦고 신뢰도가 떨어진다는 단점도 있습니다.

아두이노는 인터럽트 반응속도, Latency,가 2~4 ㎲,인데 반해, 라즈베리파이는 50~70 ㎲정도가 됩니다. 여기에 단순루프인 아두이노와 달리, 라즈베리파이는 운영체제가 돌아가고 있기 때문에 각종 예외상황과 겹쳐서 백에 한번 천에 한번 딜레이가 길어지면, 그만큼 신뢰도의 저하로 이어질 수 밖에 없습니다. 엄청 빠른 거에서 10배정도 밖에 차이가 안난다고 생각하실지 모르지만 아두이노는 16Mhz에서 작동하고, 라즈베리파이는 거의 2Ghz에서 작동합니다. 가격도 아두이노 호환보드와 라즈베리 파이를 비교하면 10배는 차이는 것을 감안하면, 마이크로컨트롤러와 마이크로프로세서의 차이를 실감할 수 있으실거라 생각합니다.


물론 초/중급자 기준으로는 별 문제가 없을 거라 생각합니다. 라즈베리파이로 모터를 몇개씩 돌리면서 인터넷을 보면서 코딩을 하지 않는 이상에는요. 아래는 아두이노와 라즈베리파이의 인터럽트 Latency에 관한 자료입니다.

라즈베리파이 인터럽트 Latency - https://raspberrypi.stackexchange.com/questions/23687/does-the-raspberry-pi-manage-hardware-interrupts

아두이노 인터럽트 Latency - https://billgrundmann.wordpress.com/2009/03/02/the-overhead-of-arduino-interrupts/


앞에서 말씀드린대로 INT0와 INT1 2개만 사용할 수 있었던 아두이노와 달리 라즈베리파이는 인터럽트를 소프트웨어로 구현하기 때문에 어떤 GPIO핀이든 인터럽트 입력으로 사용할 수 있습니다. 때문에 코드에 입력한 핀번호와만 일치한다면 GPIO핀은 원하시는 대로 변경하실 수 있습니다.


아래는 회로도입니다.



아래는 코드입니다.


import RPi.GPIO as IO
import time


encPinA = 23
encPinB = 24


IO.setmode(IO.BCM)
IO.setwarnings(False)
IO.setup(encPinA, IO.IN, pull_up_down=IO.PUD_UP)
IO.setup(encPinB, IO.IN, pull_up_down=IO.PUD_UP)


encoderPos = 0


def encoderA(channel):
    global encoderPos
    if IO.input(encPinA) == IO.input(encPinB):
        encoderPos += 1
    else:
        encoderPos -= 1
    print('PinA : %d, encoder : %d' %(channel, encoderPos))
   
def encoderB(channel):
    global encoderPos
    if IO.input(encPinA) == IO.input(encPinB):
        encoderPos -= 1
    else:
        encoderPos += 1
    print('PinB : %d, encoder : %d' %(channel, encoderPos))


IO.add_event_detect(encPinA, IO.BOTH, callback=encoderA)
IO.add_event_detect(encPinB, IO.BOTH, callback=encoderB)


while True:
    time.sleep(0.5)


약간에 설명을 드리자면요. encoderPos처럼 함수 밖에서 선언된 변수를 encoderA나 B 같은 함수 안에서 사용하려면 global이라고 선언해줘야 합니다.

인터럽트 함수는 add_event_detect를 통해 핀과 연결되어야 하고요. 매개변수로 받는 channel은 핀번호입니다. 아마 여러 개의 핀을



손으로 엔코더를 돌려봤는데 나쁘지 않네요.



마지막으로 엔코더 모터 사용시 대부분 감속기가 달린 모터(기어드 모터)를 사용하실거라 생각하는데요. 감속기가 스퍼기어인 분들은 스퍼기어는 유성기어와 달리 엔코더의 회전 방향과 감속기 축의 회전방향이 다를 수도 있다는 것을 염두해두셔야 합니다. 스퍼기어와 유성기어의 외관상 차이는 유성기어는 모터의 축과 감속기의 축이 일직선 상에 있는 반면 스퍼기어는 아래 사진처럼 감속기 축이 한쪽으로 치우쳐 있습니다.


 


이게 제가 사용한 모터인데요. 제가 분명 완벽한 설계(?)로 모터가 시계방향으로 돌도록 코드를 짰는데, 계속 반시계방향으로만 도는 일이 있었습니다. 이상해서 살펴보니 왼쪽의 엔코더 모터는 분명 시계방향으로 돌고 있는데, 감속기가 원래 그렇게 설계된 것인지 오늘쪽의 감속기 축은 반시계방향으로 돌고 있었습니다.


여기서 마치겠습니다.



메카리워즈 Image Map


모바일 버전으로 보기