iFLine-코딩으로 뽀샤버리기

9강. 바퀴의 IR 엔코더 제어하기

Posted by INDIFROG on 2020-07-02
Words 724 and Reading Time 4 Minutes
Viewed Times

9강. 바퀴의 IR 엔코더 제어하기

iFLine_irwheel_01

HeaderPin         GPIO

  LEFTF            I19
  LEFTB            I14
  RIGHTF           I13
  RIGHTB           I15

이렇게 소켓들을 연결합니다.

iFLine은 공개된 바이너리 이미지를 설치하게 되면 스마트폰으로도 제어할 수 있으며, 또한 학생들이 블로그에 공개된 소스를 기반으로 직접 자신들이 코딩해서 업로드가 가능합니다. 아두이노를 기반으로 C/C++로 코딩이 가능하고 추가적으로 MicroPython Firmware를 업로드하게 되면 Python 코딩도 가능합니다.

iFLine은 궁극적으로 창의력을 키울 수 있는 STEM 교육이 가능한 기초적인 코딩과 하드웨어 지식을 습득할 수 있는 수준의 교육에 매우 적합한 제품입니다.

라인트레이서에서 중요한 요소 중의 하나가 바로 이동거리입니다. 실제로 엔코더를 적용하지 않고도 라인트랙을 충분히 완주할 수 있습니다. 처음 배우는 초심자라면 이 부분은 건너뛰고 나중에 다시 필요할 때 학습을 하는 것이 보다 효율적일 수 있습니다.

다만, 완주 속도를 개선하고 알고리즘을 보다 효율적으로 구현하기 위해서는 엔코더에 대한 기능과 코딩에 대해 알아둘 필요가 있습니다.

정확한 이동 거리를 계산하기 위해 iFLine은 제품 버전에 따라서 2 종류의 엔코더를 제공합니다.

  • 바퀴 안쪽의 톱니 부분에 IR 센서를 적용한 엔코더
  • 모터의 뒷부분에 부착된 마그네틱 엔코더

iFLine의 제품 가격에 엔코더가 차지하는 비중이 상당히 크다고 볼 수 있습니다. 사실 바퀴에 적용된 IR 센서 보다는 마그네틱 엔코더의 정확도가 대략 4배 이상 높습니다. 따라서 가격도 거의 2배 이상 차이가 납니다.

현재 시판중인 버전은 바퀴에 적용한 엔코더 버전으로 하기 이미지와 같이 몸체의 바닥면에 배치가 되어 있습니다.

iFLine_irwheel_02

iFLine_irwheel_03

     countLeft += (lastLeftA ^ newLeftB) - (newLeftA ^ lastLeftB);

위 코드 공식을 적용해 보면 다음 표와 같이 계산이 되고 방향을 알 수 있게 됩니다.

iFLine_irwheel_04

반시계 방향으로 이동할 때는 결과값이 +1이 나옵니다. 각자 계산해 보시기 바랍니다.

실제 IFLine에서 사용중인 엔코더 코드는 다음과 같이 클래스로 작성이 되어 있습니다.

iFLine_irwheel_05

위 그림처럼 가변 저항을 가장 왼쪽으로 돌아간 상태에서 조금씩 오른쪽으로 돌려가면서 저항값을 조정하고 동시에 바퀴를 움직이게 되면 엔코더 시리얼 디버그 출력값이 일정하게 증가하는 곳을 쉽게 찾을 수 있습니다.

가변저항의 탭은 조심해서 돌려야 합니다. 꼭 적당한 십자드라이버로 서서히 돌려서 조정하기 바랍니다. 가변저항 탭이 매우 약해서 간혹 고장이 나기 쉽습니다.

iFLineEncoder.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#ifndef iFLineEncoder_h
#define iFLineEncoder_h

#include <Arduino.h>

class iFLineIREncoder
{
private:
static int8_t m_nLeA, m_nLeB, m_nReA, m_nReB;

static volatile bool lastLeftA;
static volatile bool lastLeftB;
static volatile bool lastRightA;
static volatile bool lastRightB;

static volatile bool errorLeft;
static volatile bool errorRight;

static volatile int16_t countRight, lastCountR;
static volatile int16_t countLeft, lastCountL;

private:
static void IRAM_ATTR leftISR();
static void IRAM_ATTR rightISR();

public:
static portMUX_TYPE muxl;
static portMUX_TYPE muxr;

public:
iFLineIREncoder(){};
void init(int8_t leA, int8_t leB, int8_t reA, int8_t reB);
int16_t getCountLeft();
int16_t getCountRight();

void resetCountLeft();
void resetCountRight();
void resetCounts();
};
#endif

iFLineEncoder.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
#if ARDUINO >= 100
#include "Arduino.h"
#else
#include "WProgram.h"
#endif

#include "iFLineIREncoder.h"

int8_t iFLineIREncoder::m_nLeA = GPIO_NUM_19;
int8_t iFLineIREncoder::m_nLeB = GPIO_NUM_14;
int8_t iFLineIREncoder::m_nReA = GPIO_NUM_13;
int8_t iFLineIREncoder::m_nReB = GPIO_NUM_15;

volatile bool iFLineIREncoder::lastLeftA = false;
volatile bool iFLineIREncoder::lastLeftB = false;
volatile bool iFLineIREncoder::lastRightA = false;
volatile bool iFLineIREncoder::lastRightB = false;

volatile bool iFLineIREncoder::errorLeft = false;
volatile bool iFLineIREncoder::errorRight = false;

volatile int16_t iFLineIREncoder::countRight = 0;
volatile int16_t iFLineIREncoder::lastCountR = 0;
volatile int16_t iFLineIREncoder::countLeft = 0;
volatile int16_t iFLineIREncoder::lastCountL = 0;

portMUX_TYPE iFLineIREncoder::muxl = portMUX_INITIALIZER_UNLOCKED;
portMUX_TYPE iFLineIREncoder::muxr = portMUX_INITIALIZER_UNLOCKED;

void IRAM_ATTR iFLineIREncoder::leftISR()
{
bool newLeftB, newLeftA;

portENTER_CRITICAL(&muxl);
newLeftB = digitalRead(m_nLeB);
newLeftA = digitalRead(m_nLeA);// ^ lastLeftB;

countLeft += (lastLeftA ^ newLeftB) - (newLeftA ^ lastLeftB);

if ((lastLeftA ^ newLeftA) & (lastLeftB ^ newLeftB)) {
errorLeft = true;
}

lastLeftA = newLeftA;
lastLeftB = newLeftB;

portEXIT_CRITICAL(&muxl);
}

void IRAM_ATTR iFLineIREncoder::rightISR()
{
bool newRightB, newRightA;

portENTER_CRITICAL(&muxr);
newRightB = digitalRead(m_nReB);
newRightA = digitalRead(m_nReA);// ^ lastRightB;

countRight += (lastRightA ^ newRightB) - (newRightA ^ lastRightB);

if ((lastRightA ^ newRightA) & (lastRightB ^ newRightB)) {
errorRight = true;
}
lastRightA = newRightA;
lastRightB = newRightB;

portEXIT_CRITICAL(&muxr);
}

void iFLineIREncoder::init(int8_t leA, int8_t leB, int8_t reA, int8_t reB)
{
m_nLeA = leA;
m_nLeB = leB;
m_nReA = reA;
m_nReB = reB;

pinMode(m_nLeA, INPUT);
pinMode(m_nLeB, INPUT);
pinMode(m_nReA, INPUT);
pinMode(m_nReB, INPUT);

attachInterrupt(m_nLeA, leftISR, CHANGE);
attachInterrupt(m_nLeB, leftISR, CHANGE);
attachInterrupt(m_nReA, rightISR, CHANGE);
attachInterrupt(m_nReB, rightISR, CHANGE);
}

int16_t iFLineIREncoder::getCountLeft() {
int16_t c = countLeft;
return c;
}

int16_t iFLineIREncoder::getCountRight() {
int16_t c = countRight;
return c;
}

void iFLineIREncoder::resetCountLeft(){
portENTER_CRITICAL(&muxl);
countLeft = 0;
portEXIT_CRITICAL(&muxl);
}
void iFLineIREncoder::resetCountRight(){
portENTER_CRITICAL(&muxr);
countRight = 0;
portEXIT_CRITICAL(&muxr);
}
void iFLineIREncoder::resetCounts(){
resetCountLeft();
resetCountRight();
}

다음은 main.cpp 의 코드입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <Arduino.h>
#include "iFLineIREncoder.h"

iFLineIREncoder encoderLine;

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

encoderLine.init(19, 14, 13, 15);
}

void loop()
{

portENTER_CRITICAL(&encoderLine.muxr);
Serial.println("countLeft = " + String( encoderLine.getCountLeft()) + " countRight = " + String( encoderLine.getCountRight() ));
portEXIT_CRITICAL(&encoderLine.muxr);

delay(50);
}