엔코더 모터를 사용하기 위해 로터리 엔코더를 사용해봤다.
http://blog.daum.net/_blog/BlogTypeView.do?blogid=04jfq&articleno=1906598
http://www.dx.com/p/bonatech-360-rotary-encoder-module-for-arduino-black-310402
내가 사용하는 Rotary Encoder이다.
보면은 CLK DT SW + GND라고 되어있다.
여기서 +와 GND는 전원핀
SW는 버튼 신호선
그리고 CLK랑 DT가 로터리 엔코더 펄스 핀이다.
첫 번째 사진이 로터리 엔코더를 돌렸을 때
발생하는 펄스 인데
각각 CLK랑 DT에서 발생된다.
내가 사용하는 로터리 엔코더는 1스탭당 CLK랑 DT펄스 1개씩 생성된다.
그래서 아두이노 우노에 달려있는 atmega328P-PU의 인터럽트 기능을 이용할 것이다.
회로를 편하게 하기 위해서 아래와 같이 회로를 구성했다.
위엄한 방법이지만 잠시동안만 사용할 것이라 문제는 없어보인다.
GND - GND
+ - D13 (PB5)
SW - D12 (PB4)
DT - D11 (PB3)
CLK - D10 (PB2)번에 연결했다.
atmega382p pu데이터 시트를 보면
PB4는 PCINT4
PB3는 PCINT3
PB2는 PCINT2라고 써있는데
이것은 PB4:2핀들이 Pin Change Interrupt기능이 가능하다는 소리이다.
이 사진은 atmega328에 있는 interrupt들인데
PCINT0 ~ 7까지는 4번째 인터럽트인 PCINT0을 이용한다.
위의 사진은 Pin Change Interrupt Control Register(PCICR)이다.
세 번째를 해석해보면
Bit 0 - PCIE: Pin Change Interrupt Enable 0
PCIE0 비트가 세트(1)로 되어있고 상태 레지스터(SREG)에 있는 I 비트가 세트(1)로 되어있다면,
pin change interrupt 0이 사용 가능해진다. 사용 가능한 PCINT7 ~ 0사이의 어떤 핀들의 상태 변화가
인터럽트를 발생시킬것이다. Pin Change Request에 해당하는 인터럽트는 PCI0인터럽트 벡터로 부터 실행된다.
PCINT7 ~ 0핀들은 PCMSK0레지스터에 의해 각각 사용 가능해진다.
여기서 SREG의 I비트는 sei();로 세트 시킬 수 있다.
그리고 PCICR레지스터의 PCIE0 비트를 세트(1)시켜야 하므로
int main(void)의 첫번째 줄에 PCICR |= (1 << PCIE0);를 추가하자
그리고 위의 설명으로 보면 PCMSK0레지스터도 설정해야한다.
위의 사진은 PCMSK0에 관한 데이터 시트인데
해석하면
Bits 7 ~ 0 - PCINTn: Pin Change Enable Mask [n = 7 ~ 0]
각각의 PCINT 7 ~ 0 비트들은 pin change interrup가 해당하는 입/출력 핀에의해 사용 가능하게 되는지를
선택한다.
만약 PCINT7 ~ 0이 세트(1)되고 PCICR에 있는 PCIE0 비트가 세트(1)되어있다면,
pin change interrupt는 해당하는 입/출력 핀에의해 사용 가능해진다.
만약 PCINT7 ~ 0가 클리어(0)으로 되어있으면, 해당하는 입/출력 핀에 대한 pin change interrupt는 사용못한다.
쉽게 말하자면 우리는 PCINT4 ~ 2를 사용할 것이므로
PCINT4 ~ 2비트들을 세트(1)면 된다는 뜻이다.
아까 작성한 PCICR 밑에다가
PCMSK0 |= (1 << PCINT4) | (1 << PCINT3) | (1 << PCINT2);를 추가하자
그리고 우리는 PB4 ~ 2번 핀들을 입력으로 사용할 것이므로
DDRB &= ~((1 << PB4) | (1 << PB3) | (1 << PB2));를 추가한 다음
D13(PB5)가 +에 연결되어 있기 때문에
DDRB |= (1 << PB5);
PORTB |= (1 << PB5);도 추가하자
그리고 마지막으로 sei();를 추가하면 준비는 끝난다.(sei는 모든 인터럽트를 사용 가능하게 하는것)
그런 다음 펄스의 수를 카운팅 해서 회전수를 측정하기 때문에
volatile int pulse = 0;으로 pulse라는 변수를 만들어두자
그리고 추가적으로 volatile pA, pB;도 만들어두자
우리가 사용할 인터럽트는 PCINT0이므로
ISR(PCINT0_vect) {
}라고 만들어 두고
PCINT7 ~ 0사이의 어떠한 변화가 있으면 PCINT0인터럽트가 발생하여
위의 구문 안의 것들이 실행된다.
일단 clk(pb2)와 dt(pb3)의 값을 알아야 하므로 PINB를
따로 data라는 변수에 담았다.
int data = PINB;
그런다음 아까 선언한 pA는 clk의 전 상태이고
만약 전 clk상태와 지금의 clk상태가 다르다면 이라는 if 문을 만들어준다.
if(pA != (data & (1 << PINB2))) {
그리고 여기서 만약 clk가 0이라는 if문을 만들어준다.
if(!(data & (1 << PINB2))) {
여기서 2가지로 나뉘는데
dt값도 0일때와 아닐 때로 나뉜다.
따라서
if(!(data & (1 << PINB3)))와
else 를 추가하고
각각에 pulse--;
pulse+;를 추가하자
그러면
1 2 3 4 5 6 | if(pA != (data & (1 << PINB2))) { if(!(data & (1 << PINB2))) { if(!(data & (1 << PINB3))) pulse--; else pulse++; } } | cs |
1 2 3 4 5 6 7 8 9 | if(pA != (data & (1 << PINB2))) { if(!(data & (1 << PINB2))) { if(!(data & (1 << PINB3))) pulse--; else pulse++; } else { if(!(data & (1 << PINB3))) pulse++; else pulse--; } } | cs |
그리고 마지막에 pA랑 pB을 갱신하주는 코드를 추가해주면 끝난다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | ISR(PCINT0_vect) { data = PINB; if (pA != (data & (1 << PINB2))) { if (!(data & (1 << PINB2))) { if (!(data & (1 << PINB3))) pulse--; else pulse++; } else { if (!(data & (1 << PINB3))) pulse++; else pulse--; } } if (pB != (data & (1 << PINB3))) { if (!(data & (1 << PINB3))) { if (!(data & (1 << PINB2))) pulse++; else pulse--; } else { if (!(PINB & (1 << PINB2))) pulse--; else pulse++; } } pB = data & (1 << PINB3); pA = data & (1 << PINB2); button = (data & (1 << PINB4)) >> PINB4; } | cs |
button은 버튼 상태도 알고싶었기에 추가했다.
그런다음 pulse값을 보면 1스탭당 4씩 증가하고 0.5스탭이면 2
0.25스탭이면 1씩 증가하는 것을 볼 수 있다.
(나는 아두이노 우노로 했기 때문에 Serial.println(pulse);로 확인했다)
질문은 댓글에 남겨주면 빠른 시간 안에 답변을 달것이다.
(비공개 말고 공개로 하면 다른 사람도 알아 가는 것이 있기 때문에 질문은 가능한 공개로 설정)
'atmega328(아두이노 미니 이용)' 카테고리의 다른 글
스터디 상생 플러스 1 - 2 (필요한 C언어 문법) (0) | 2020.04.09 |
---|---|
스터디 상생 플러스 1 - 1 (MCU에 대한 지식 습득) (0) | 2020.04.09 |
비트 연산자 테스트 04 (0) | 2017.08.12 |
atmega 비트연산자 03 (0) | 2017.08.11 |
아두이노로 atmega 사용하기 02 (2) | 2017.08.10 |