iFLine-코딩으로 뽀샤버리기

11강. Battery Check

Posted by INDIFROG on 2020-07-03
Words 830 and Reading Time 5 Minutes
Viewed Times

11강. Battery Check

iFLine_battery_01

  HeaderPin         GPIO

  BATADC            I39

이번 시간은 앞서 IRSensor 강의에서 다룬 적이 있는 ESP32의 ADC 값을 읽어 오는 방법에 대해 자세히 알아 보고자 합니다.

ESP32는 12 비트 SAR (Successive Approximation Register) ADC 2종류를 제공하며, 총 18 개의 측정 채널 (아날로그 가능 핀)을 사용할 수 있습니다.

즉, ADC 드라이버는

  • ADC1 (8 채널, GPIO 32-39에 연결됨)
  • ADC2 (10 채널, GPIO 0, 2, 4, 12 - 15 및 25 - 27에 연결됨)

를 지원합니다.

​하기 최신 ESP32-PICO-V3 MCU GPIO 맵은 기존 ESP32-PICO-D4 MCU와 비교해서 일부 변경된 곳이 있습니다. 즉, PIN to PIN호환이 되지 않습니다. IO23, IO18가 없어지고 IO17이 IO20, IO16이 SDIOVDD로 변경된 사례가 그것입니다.

ADC2는 몇 가지 제한 사항을 숙지하고 회로 설계를 해야 합니다.​ 즉, ADC2는 Wi-Fi 드라이버에서 사용하기 때문에 Wi-Fi 를 사용하는 경우에는 ADC2 기능을 사용할 수 없습니다.
또한 일부 ADC2 핀(GPIO 0, 2, 4, 12, 15)들은 부팅 시 관여하는 데 사용되므로 역시 사용시에 주의를 해야 합니다. 특히, IO12 등은 입력이나 인터럽트에 사용할 수 없습니다.

배터리 체크를 위해 ADC1의 GPIO39 을 사용하는 방법을 알아보도록 하겠습니다.​

adc1_config_width () : 정밀도 혹은 해상도
adc1_config_channel_atten () : 감쇠 정도

ESP32_PICO_D4 Pin Map

iFZeropinmap_D4

ESP32_PICO_V3 Pin Map

iFZeropinmap_V3

​​ADC 초기화

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void init(){
// initialize ADC
pinMode(m_nPin, INPUT_PULLDOWN);
m_channel = getChannelforADC1( m_nPin );

adc1_config_width( m_adcBit );
adc1_config_channel_atten((adc1_channel_t)m_channel, atten);
esp_adc_cal_value_t val_type = esp_adc_cal_characterize(
unit,
atten,
m_adcBit,
DEFAULT_VREF,
&adc_chars);

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

ADC 읽기

adc1_get_raw () 함수를 사용하여 ADC 변환 결과를 읽을 수 있습니다.

1
2
3
4
5
6
7
8
9
double getValue(){

m_nADCRaw = adc1_get_raw((adc1_channel_t)m_channel);
// printf("Battery = %d \n", m_nADCRaw);

uint32_t voltage = esp_adc_cal_raw_to_voltage(m_nADCRaw, &adc_chars);
double avgDV = m_nADCRaw * 4.2/4096;
return avgDV;
}

m_nADCRaw = ADC1 값

12bit = 4096

충전배터리의 최대 충전전압 = 4.2

m_nADCRaw * 4.2/4096;

따라서 위 수식에 의해 전압값을 유도할 수 있습니다.

풀소스를 올려 놓습니다. C++ 클래스로 작성을 했습니다. 소스의 간결함을 위해 .h 파일로만 클래스를 구성합니다. 따라서 이러한 방식으로 빌드를 할 경우 반드시 Clean을 하고서 빌드하시기 바랍니다. 헤더파일에 변경이 있더라도 .cpp가 변경이 없으면 새롭게 갱신하지 않습니다.

이 부분이 좀 불편하긴 하지만, 소스의 유지관리가 간결하기 때문에 헤더 파일로만 클래스 라이브러리를 구성하고 개발하는 것이 임베디드는 한결 편리하긴 합니다. 다만, 인터럽트와 같이 static 함수나 변수를 선언해야 하는 경우는 어쩔 수 없이 cpp파일을 만들어야 합니다.

​iFBattery.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
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
#ifndef iFBattery_v1_h
#define iFBattery_v1_h
#define iFBATTERY_LIBRARY_VERSION 1.1.1

#include <Arduino.h>
#include "driver/adc.h"
#include "esp_adc_cal.h"
#include "driver/mcpwm.h" //inclui a biblioteca "Motor Control PWM" nativa do ESP32

/*
typedef enum {
ADC_WIDTH_BIT_9 = 0, // ADC capture width is 9Bit
ADC_WIDTH_BIT_10 = 1, // ADC capture width is 10Bit
ADC_WIDTH_BIT_11 = 2, // ADC capture width is 11Bit
ADC_WIDTH_BIT_12 = 3, // ADC capture width is 12Bit
ADC_WIDTH_MAX,
} adc_bits_width_t;
*/
/*
typedef enum {
ESP_ADC_CAL_VAL_EFUSE_VREF = 0, // Characterization based on reference voltage stored in eFuse
ESP_ADC_CAL_VAL_EFUSE_TP = 1, // Characterization based on Two Point values stored in eFuse
ESP_ADC_CAL_VAL_DEFAULT_VREF = 2, // Characterization based on default reference voltage
} esp_adc_cal_value_t;
*/

/*
typedef enum {
ADC1_CHANNEL_0 = 0, // ADC1 channel 0 is GPIO36
ADC1_CHANNEL_1, // ADC1 channel 1 is GPIO37
ADC1_CHANNEL_2, // ADC1 channel 2 is GPIO38
ADC1_CHANNEL_3, // ADC1 channel 3 is GPIO39
ADC1_CHANNEL_4, // ADC1 channel 4 is GPIO32
ADC1_CHANNEL_5, // ADC1 channel 5 is GPIO33
ADC1_CHANNEL_6, // ADC1 channel 6 is GPIO34
ADC1_CHANNEL_7, // ADC1 channel 7 is GPIO35
ADC1_CHANNEL_MAX,
} adc1_channel_t;
*/
//#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


class iFBattery{
private:
uint8_t m_nPin;
uint16_t m_nADCRaw;

adc_bits_width_t m_adcBit;
adc1_channel_t m_channel;

const adc_atten_t atten = ADC_ATTEN_DB_0; // 1/1
const adc_unit_t unit = ADC_UNIT_1;
esp_adc_cal_characteristics_t adc_chars;

public:
iFBattery(){};
iFBattery(uint8_t gpio, adc_bits_width_t bit = ADC_WIDTH_12Bit){
m_nPin = gpio;
m_adcBit = bit;
}

void init(){
// initialize ADC
pinMode(m_nPin, INPUT_PULLDOWN);
m_channel = getChannelforADC1( m_nPin );

adc1_config_width( m_adcBit );
adc1_config_channel_atten((adc1_channel_t)m_channel, atten);
esp_adc_cal_value_t val_type = esp_adc_cal_characterize(
unit,
atten,
m_adcBit,
DEFAULT_VREF,
&adc_chars);

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

double getValue(){

m_nADCRaw = adc1_get_raw((adc1_channel_t)m_channel);
// printf("Battery = %d \n", m_nADCRaw);

uint32_t voltage = esp_adc_cal_raw_to_voltage(m_nADCRaw, &adc_chars);
double avgDV = m_nADCRaw * 4.2/4096;
return avgDV;
}

adc1_channel_t 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;
}
};

#endif

main.cpp

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

iFBattery ifBattery(GPIO_NUM_39);

void setup() {
// put your setup code here, to run once:
ifBattery.init();
}

void loop() {
// put your main code here, to run repeatedly:
double value = ifBattery.getValue();
printf("Battery Voltage = %f \n", value);

delay(1000);
}

GPIO39번 핀에 연결된 배터리 라인입니다.

충전이 완충되면 거의 4.2 V에 가깝게 됩니다.
iFLine으로 센서와 모터를 구동하고 동작하는 기초적인 코딩 과정은 거의 마무리 된 것 같습니다.

iFLine_battery_03

다음편부터는 지금까지 배운 내용들을 통합하고 FreeRTOS로 어떻게 적용하는가 등에 대한 내용들, 그리고 플래쉬 메모리를 활용하고 WiFi 및 Bluetooth와 연동하여 openFrameworks 등과 디버깅하는 방법들에 대한 각종 고급 과정을 진행할까 합니다.

또한 iF시리즈에 통합된 멀티위 프로토콜에 대한 공개와 더불어 스마트폰과 연동하기 위한 기본 기능들에 대한 강좌도 준비를 하겠습니다.