iFBalance-코딩으로 뽀샤버리기

9강. IR 센서 동작하기

Posted by INDIFROG on 2021-03-09
Words 983 and Reading Time 6 Minutes
Viewed Times

9강. IR 센서 동작하기

iFBalance의 IR센서는 모두 7조로 구성이 되어 있습니다.
1조는 발광부/수광부 쌍으로 구성이 되어 있고 iFLine의 IR 센서부와는 다르게 한번에 동시에 켜지는 방식이 아닙니다.

VSCODE_LIB

안정적으로 움직이는 iFLine과는 다르게 iFBalance는 중심을 잡기 위해 앞뒤로 혹은 옆으로도 조금씩 움직이는 구조이다 보니 iFBalance의 중심부에 5조의 센서들이 모여 있도록 설계가 되어 있습니다.
이 때문에 IR 빛을 조사하게 되면 상호간의 검출 민감도가 매우 크게 동작을 해서 몇개씩 그룹을 지어서 순차적으로 발광 및 수신하도록 회로를 설계했습니다.

즉, 하기 코드 처럼

GPIO5번은 맨 좌측 센서, 가운데 센서, 맨 우측 센서를 ON/OFF 하고
GPIO20번은 좌측 앞 센서, 우측 뒤 센서를 ON/OFF 하고
GPIO4번은 좌측 뒤 센서, 우측 앞 센서를 ON/OFF 합니다.

센서들은 발광한 다음 100 마이크로초 지연후 ADC를 수행합니다.

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
digitalWrite( 5, HIGH );  // LL, CC, RR
digitalWrite( 20, LOW ); // LF, RB
digitalWrite( 4, LOW ); // LB, RF

delayMicroseconds( 100 );
m_nADCRaw[0] = adc1_get_raw((adc1_channel_t)m_channels[0]);
m_nADCRaw[3] = adc1_get_raw((adc1_channel_t)m_channels[3]);
m_nADCRaw[6] = adc1_get_raw((adc1_channel_t)m_channels[6]);


digitalWrite( 5, LOW );
digitalWrite( 20, LOW );
digitalWrite( 4, HIGH ); // LB, RF
delayMicroseconds( 100 );

m_nADCRaw[2] = adc1_get_raw((adc1_channel_t)m_channels[2]);
m_nADCRaw[4] = adc1_get_raw((adc1_channel_t)m_channels[4]);


digitalWrite( 5, LOW );
digitalWrite( 20, HIGH ); // LF, RB
digitalWrite( 4, LOW );
delayMicroseconds( 100 );

m_nADCRaw[1] = adc1_get_raw((adc1_channel_t)m_channels[1]);
m_nADCRaw[5] = adc1_get_raw((adc1_channel_t)m_channels[5]);

digitalWrite( 20, LOW ); // LF, RB

각각의 GPIO에 할당된 ADC 채널은 다음과 같습니다.

1
2
3
4
5
6
7
8
case GPIO_NUM_36: channel = ADC1_CHANNEL_0; break;
case GPIO_NUM_37: channel = ADC1_CHANNEL_1; break;
case GPIO_NUM_38: channel = ADC1_CHANNEL_2; break;
case GPIO_NUM_39: channel = ADC1_CHANNEL_3; break;
case GPIO_NUM_32: channel = ADC1_CHANNEL_4; break;
case GPIO_NUM_33: channel = ADC1_CHANNEL_5; break;
case GPIO_NUM_34: channel = ADC1_CHANNEL_6; break;
case GPIO_NUM_35: channel = ADC1_CHANNEL_7; break;

위 IR센서의 수광부를 위해 동작하는 GPIO들은 모두 ADC 1번 채널을 사용하고 있습니다.

다음은 IR센서를 구동하는 iFIRSensor 동작 소스코드입니다.

iFIRSensor.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
111
112
113
114
115
116
117
118
119
120
121
122
123
#include <Arduino.h>
#include "iFIRSensor.h"
#include "iFPID.h"

TaskHandle_t iFIRSensor::taskHandle = NULL;
portMUX_TYPE iFIRSensor::mux = portMUX_INITIALIZER_UNLOCKED;
SemaphoreHandle_t iFIRSensor::xMutex;
uint16_t iFIRSensor::m_nADCRaw[ SENSOR_COUNT ];
adc1_channel_t iFIRSensor::m_channels[ SENSOR_COUNT ];
portTickType iFIRSensor::delayTasks = 1000;



iFIRSensor::iFIRSensor(){
m_adcBit = ADC_WIDTH_12Bit;
}

uint16_t iFIRSensor::get(const int id){
return m_nADCRaw[id];
}

void iFIRSensor::calc(){

digitalWrite( 5, HIGH ); // LL, CC, RR
digitalWrite( 20, LOW );
digitalWrite( 4, LOW );
delayMicroseconds( 100 );
m_nADCRaw[0] = adc1_get_raw((adc1_channel_t)m_channels[0]);
m_nADCRaw[3] = adc1_get_raw((adc1_channel_t)m_channels[3]);
m_nADCRaw[6] = adc1_get_raw((adc1_channel_t)m_channels[6]);


digitalWrite( 5, LOW );
digitalWrite( 20, LOW );
digitalWrite( 4, HIGH ); // LB, RF
delayMicroseconds( 100 );

m_nADCRaw[2] = adc1_get_raw((adc1_channel_t)m_channels[2]);
m_nADCRaw[4] = adc1_get_raw((adc1_channel_t)m_channels[4]);


digitalWrite( 5, LOW );
digitalWrite( 20, HIGH ); // LF, RB
digitalWrite( 4, LOW );
delayMicroseconds( 100 );

m_nADCRaw[1] = adc1_get_raw((adc1_channel_t)m_channels[1]);
m_nADCRaw[5] = adc1_get_raw((adc1_channel_t)m_channels[5]);

digitalWrite( 20, LOW ); // LF, RB
}

void iFIRSensor::taskSensor(void *pvParameter) {

while(1){

calc();
vTaskDelay( 4 );
}
}

int iFIRSensor::analogRead(int id)
{
m_nADCRaw[id] = adc1_get_raw((adc1_channel_t)m_channels[id]);

return m_nADCRaw[id];
}



void iFIRSensor::init(){
// initialize ADC

pinMode( 5, OUTPUT);
pinMode( 20, OUTPUT);
pinMode( 4, OUTPUT);

digitalWrite( 5, LOW );
digitalWrite( 20, LOW );
digitalWrite( 4, HIGH );


for(int i=0; i<SENSOR_COUNT; i++){
pinMode(m_nPins[i], INPUT_PULLDOWN);
m_channels[i] = getChannelforADC1( m_nPins[i] );
adc1_config_channel_atten((adc1_channel_t)m_channels[i], atten);
}

adc1_config_width( m_adcBit );

esp_adc_cal_value_t val_type = esp_adc_cal_characterize(
unit,
atten,
m_adcBit,
DEFAULT_VREF,
&adc_chars);

xTaskCreatePinnedToCore(&taskSensor,
"taskSensor",
1024,
(void*)&delayTasks,
uxTaskPriorityGet(NULL),
&taskHandle,
0);

printf("val_type = %d \n", val_type);
}

adc1_channel_t iFIRSensor::getChannelforADC1(int gpio){
adc1_channel_t channel = ADC1_CHANNEL_0;
switch( gpio ){
case GPIO_NUM_36: channel = ADC1_CHANNEL_0; break;
case GPIO_NUM_37: channel = ADC1_CHANNEL_1; break;
case GPIO_NUM_38: channel = ADC1_CHANNEL_2; break;
case GPIO_NUM_39: channel = ADC1_CHANNEL_3; break;
case GPIO_NUM_32: channel = ADC1_CHANNEL_4; break;
case GPIO_NUM_33: channel = ADC1_CHANNEL_5; break;
case GPIO_NUM_34: channel = ADC1_CHANNEL_6; break;
case GPIO_NUM_35: channel = ADC1_CHANNEL_7; break;
}

return channel;
}

iFIRSensor 센서 클래스는 이전 iF 클래스들과 마찬가지로 init() 함수를 가지고 있습니다.
init() 함수에서는 GPIO 초기화와 ADC1에 대한 채널 할당, 그리고 xTaskCreatePinnedToCore 함수를 호출해서 태스크에서 스케쥴링을 합니다.

한가지 주의할 점이 있습니다.

실제 ESP32 FreeRTOS 를 사용하는 펌웨어에서 가급적이면 빠른 스피드로 제어 기능을 구현할 때엔 테스크의 갯수를 최소로 하는 것이 좋습니다. 태스크의 루프는 일정 시간을 소모합니다.
iFBalance 경우엔 3개 정도를 사용하는데 그 이상을 사용하게 되면 MPU 센싱하는 메인 루프의 속도가 크게 저하가 됩니다.

참고하시기 바랍니다.

iFIRSensor.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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#pragma once

#include <Arduino.h>
#include "driver/adc.h"
#include "esp_adc_cal.h"
#include "driver/mcpwm.h"

//#define DEFAULT_VREF 0 //Use adc2_vref_to_gpio() to obtain a better estimate
#define DEFAULT_VREF 1100 //Use adc2_vref_to_gpio() to obtain a better estimate
#define SENSOR_COUNT 7

class iFIRSensor{
private:

uint8_t m_nPins[ SENSOR_COUNT ];


static TaskHandle_t taskHandle;
static uint16_t m_nADCRaw[ SENSOR_COUNT ];
static adc1_channel_t m_channels[ SENSOR_COUNT ];

public:
static portMUX_TYPE mux;
static SemaphoreHandle_t xMutex;
static portTickType delayTasks;
private:
adc_bits_width_t m_adcBit;
const adc_atten_t atten = ADC_ATTEN_DB_6; // 1/1
const adc_unit_t unit = ADC_UNIT_1;
esp_adc_cal_characteristics_t adc_chars;

public:
iFIRSensor();
iFIRSensor(uint8_t pin1, uint8_t pin2, uint8_t pin3, uint8_t pin4, uint8_t pin5, uint8_t pin6, uint8_t pin7,
adc_bits_width_t bit = ADC_WIDTH_12Bit){
m_nPins[0] = pin1;
m_nPins[1] = pin2;
m_nPins[2] = pin3;
m_nPins[3] = pin4;
m_nPins[4] = pin5;
m_nPins[5] = pin6;
m_nPins[6] = pin7;

m_adcBit = bit;
}

void init();
void setPin(const int id, uint8_t pin){
m_nPins[id] = pin;
}

void setADCBits(adc_bits_width_t bit = ADC_WIDTH_12Bit){
m_adcBit = bit;
}

static void calc();
static void taskSensor(void *pvParameter);

uint16_t get(int id);

int analogRead(int id);
adc1_channel_t getChannelforADC1(int gpio);
};

main.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
///////////////////////////////////////////////////////
// for IR Sensor
iFIRSensor ifIRSensor( 37, 34, 36, 32, 35, 33, 38, ADC_WIDTH_BIT_12);
uint16_t m_irValue[5];
uint16_t m_irPrevValue[5];
int16_t m_irDif[5];

void setup()
{

....

///////////////////////////////////////////////////////
// IR Sensor
ifIRSensor.init();
}


int irLoop = 0;
void loop() {
bool isIMUUpdate = false;
uint32_t distance = 0;
if (xSemaphoreTake( iFVL53LX::xMutex, portMAX_DELAY ) == pdTRUE){
isIMUUpdate = ifMpu.update();
distance = ifVL53LX.getDistance();
}
xSemaphoreGive( iFVL53LX::xMutex );

if (isIMUUpdate) {

....

///////////////////////////////////////////////////////
// 시리얼 출력이 많으면 루프 속도가 떨어진다.
if( irLoop++ > 50 ){
irLoop = 0;
for( int i=0; i<7; i++){
m_irValue[i] = ifIRSensor.get(i);
Serial.printf("[%d]= %d ", i , m_irValue[i]);
}
Serial.println(" ");
}

}
}

수신된 센서값은 흰색 라인에서는 값이 작아지고 검은색 영역에서는 값이 크게 나타납니다.

iFBalance 9강 소스 파일 : iFBalance_09.zip

iFBalance
iFBalance IR Sensor 동작 영상
Shoot by Serimo on 2021-3-09
iFBalance IR Senso 동작 영상