레이블이 Embedded인 게시물을 표시합니다. 모든 게시물 표시
레이블이 Embedded인 게시물을 표시합니다. 모든 게시물 표시

2019년 6월 14일 금요일

ESP32에서 CAN bus 사용하기

CAN버스는 Controller Area Network를 말하는데 이 프로토콜은 주로 자동차에서 널리 이용되고 있다. CAN에 대해 더 자세히 알고 싶으면 다음의 링크들을 참고하면 된다.

- http://www.ni.com/white-paper/2732/en
- http://www.ti.com/lit/an/sloa101b/sloa101b.pdf

ESP32는 CAN 인터페이스를 지원하기 때문에 CAN bus를 쉽게 사용할 수 있다.
이 데모에서는 2개의 ESP32 모듈을 사용해, 첫번째 모듈은 'hellocan' 이라는 메시지를 보내고 두번째 모듈은 받은 메시지의 문자열을 전부 대문자로 바꿔 첫번째 모듈로 돌려보낸다. 그러면 첫번째 모듈은 받은 메시지를 터미널로 출력하게 된다.

ESP32는 CAN controller만을 내장하고 있기 때문에 CAN 버스를 사용하려면 CAN transceiver가 필요하다. 아래 사진의 CAN transceiver를 2개 사용했다.

* aliexpress에서 개당 약 $1 정도로 구입할 수 있다.

ESP32와 CAN transceiver 모듈의 연결은 다음과 같이 해 주면 된다.


CAN library로는 Thomas Barth가 만든 CAN driver를 사용했다.

https://github.com/ThomasBarth/ESP32-CAN-Driver/tree/master/components/can

라이브러리를 다운받아 설치해주면 된다.

첫번째 ESP32 (위의 그림에서 ESP32 (1)에 해당)에는 아래와 같은 코드를 넣어준다.

#include <ESP32CAN.h>
#include <CAN_config.h>

/* the variable name CAN_cfg is fixed, do not change */
CAN_device_t CAN_cfg;

void setup() {
    Serial.begin(115200);
    Serial.println("iotsharing.com CAN demo");
    /* set CAN pins and baudrate */
    CAN_cfg.speed=CAN_SPEED_1000KBPS;
    CAN_cfg.tx_pin_id = GPIO_NUM_5;
    CAN_cfg.rx_pin_id = GPIO_NUM_4;
    /* create a queue for CAN receiving */
    CAN_cfg.rx_queue = xQueueCreate(10,sizeof(CAN_frame_t));
    //initialize CAN Module
    ESP32Can.CANInit();
}

void loop() {
    CAN_frame_t rx_frame;
    //receive next CAN frame from queue
    if(xQueueReceive(CAN_cfg.rx_queue,&rx_frame, 3*portTICK_PERIOD_MS)==pdTRUE){

      //do stuff!
      if(rx_frame.FIR.B.FF==CAN_frame_std)
        printf("New standard frame");
      else
        printf("New extended frame");

      if(rx_frame.FIR.B.RTR==CAN_RTR)
        printf(" RTR from 0x%08x, DLC %d\r\n",rx_frame.MsgID,  rx_frame.FIR.B.DLC);
      else{
        printf(" from 0x%08x, DLC %d\n",rx_frame.MsgID,  rx_frame.FIR.B.DLC);
        /* convert to upper case and respond to sender */
        for(int i = 0; i < 8; i++){
          if(rx_frame.data.u8[i] >= 'a' && rx_frame.data.u8[i] <= 'z'){
            rx_frame.data.u8[i] = rx_frame.data.u8[i] - 32;
          }
        }
      }
      //respond to sender
      ESP32Can.CANWriteFrame(&rx_frame);
    }
}

두번째 ESP32 (위의 그림에서 ESP32 (2)에 해당)에는 아래와 같은 코드를 넣어준다.

#include <ESP32CAN.h>
#include <CAN_config.h>

/* the variable name CAN_cfg is fixed, do not change */
CAN_device_t CAN_cfg;

void setup() {
    Serial.begin(115200);
    Serial.println("iotsharing.com CAN demo");
    /* set CAN pins and baudrate */
    CAN_cfg.speed=CAN_SPEED_1000KBPS;
    CAN_cfg.tx_pin_id = GPIO_NUM_5;
    CAN_cfg.rx_pin_id = GPIO_NUM_4;
    /* create a queue for CAN receiving */
    CAN_cfg.rx_queue = xQueueCreate(10,sizeof(CAN_frame_t));
    //initialize CAN Module
    ESP32Can.CANInit();
}

void loop() {
    CAN_frame_t rx_frame;
    //receive next CAN frame from queue
    if(xQueueReceive(CAN_cfg.rx_queue,&rx_frame, 3*portTICK_PERIOD_MS)==pdTRUE){

      //do stuff!
      if(rx_frame.FIR.B.FF==CAN_frame_std)
        printf("New standard frame");
      else
        printf("New extended frame");

      if(rx_frame.FIR.B.RTR==CAN_RTR)
        printf(" RTR from 0x%08x, DLC %d\r\n",rx_frame.MsgID,  rx_frame.FIR.B.DLC);
      else{
        printf(" from 0x%08x, DLC %d\n",rx_frame.MsgID,  rx_frame.FIR.B.DLC);
        for(int i = 0; i < 8; i++){
          printf("%c\t", (char)rx_frame.data.u8[i]);
        }
        printf("\n");
      }
    }
    else
    {
      rx_frame.FIR.B.FF = CAN_frame_std;
      rx_frame.MsgID = 1;
      rx_frame.FIR.B.DLC = 8;
      rx_frame.data.u8[0] = 'h';
      rx_frame.data.u8[1] = 'e';
      rx_frame.data.u8[2] = 'l';
      rx_frame.data.u8[3] = 'l';
      rx_frame.data.u8[4] = 'o';
      rx_frame.data.u8[5] = 'c';
      rx_frame.data.u8[6] = 'a';
      rx_frame.data.u8[7] = 'n';

      
      ESP32Can.CANWriteFrame(&rx_frame);
    }
}

양쪽의 보드를 동작시키고 첫번째 ESP32의 시리얼 터미널의 출력은 다음과 같다.




* Original credit goes to http://www.iotsharing.com/2017/09/how-to-use-arduino-esp32-can-interface.html



2019년 4월 26일 금요일

아두이노 우노에서 ADXL335 가속도 센서 사용하기 (Using ADXL335 accelerometer on Arduino Uno)

Original Document: https://lastminuteengineers.com/adxl335-accelerometer-arduino-tutorial/

 

가속도 센서의 동작 원리

가속도 센서의 동작원리를 이해하려면 3차원 큐브 속에 공이 들어 있는걸 상상하면 된다.



만일 이 큐브가 우주에 있어 모든것이 무게가 없는 상태라고 한다면 공은 큐브 가운데에 가만히 떠 있을 것이다. 위의 그림에서 큐브의 각 벽면이 특정 축을 나타낸다고 생각해 보자.

박스가 갑자기 왼쪽으로 가속도 1g의 가속도로 가속되어 움직이면 공은 큐브의 X 벽에 부딛히게 될 것이다. 공이 벽X를 때리는 힘을 측정한다면 X축에 1G의 출력값을 얻을 수 있다.



만일 큐브를 지표면으로 가져온다면 공은 벽 Z로 떨어질것이고 벽Z에 1G의 힘을 가할 것이다.




이 경우 큐브는 움직이지 않았지만 그래도 Z축으로 1G의 값을 얻었다. 이는 중력 가속도가 1G의 힘으로 공을 아래로 당기고 있기 때문이다.

* 가속도 센서는 기울어짐 측정 어플리케이션에서 정적 가속도(static acceleration) 뿐 아니고 모션, 쇼크, 진동에 의한 동적 가속도(dynamic acceleration)도 측정한다.

MEMS 가속도 센서의 동작 원리

MEMS(Micro Electro Mechanical Systems) 가속도 센서는 실리콘 웨이퍼 위에 매주 작은 기계가공된 구조(micro-machined structure)로 이루어져 있다.



이 구조는 폴리실리콘 스프링에 매달려 있다. 특정 축에 가속도가 가해지면 이 구조가 편향될 수 있게 해 준다.

편향으로 인해 고정된 플레이트와 매달려 있는 구조 사이의 정전용량(capacitance)이 바뀌게 된다. 이 정전용량의 변화는 축에 가해진 가속도에 비례한다.

센서는 정전용량의 변화를 처리해 아날로그 출력전압으로 변환한다.

ADXL335 가속도 센서의 하드웨어

모듈의 핵심은 아날로그 디바이스에서 나온 소형, 저전력, 저잡음의 3축 MEMS 가속도 센서인 ADXL335이다. 이 센서는 +-3g 범위 내의 값을 측정할 수 있다. 기울기 감지 어플리케이션에서 중력가속도로 인한 정적 가속도 뿐 아니고 모션, 쇼크, 진동에 의한 동적 가속도도 측정할 수 있다.



이 센서는 1.8~3.6V DC에서 동작하고 약 350uA의 전류를 소비한다. 하지만 모듈에 3.3V 레귤레이터가 들어있기 때문에 아두이노같이 5V를 사용하는 마이크로 컴트롤러에서 사용하는데도 문제가 없다.

빵판에서도 사용하기 쉽게 ADXL335의 모든 핀이 2.54mm 피치의 6핀 헤더로 연결되어 있다. 여기에는 X, Y, Z 3축의 아날로그 출력, 2개의 전원핀, 셀프테스트 핀이 들어 있다.

아날로그 출력은 ratiometic한데 이건 즉 0g는 3.3V 공급 전압의 중간(1.65V)을 출력하고, -3g는 0V, 3g는 3.3V를 출력하고 그 사이 값은 정확하게 비례한 전압이 출력된다.

다음은 ADXL335 가속도 센서 IC의 풀 스펙이다.



더 상세한 내용은 데이터쉬트를 참조하면 된다.


ADXL335 가속도 센서 핀아웃




아두이노 우노와 연결

연결은 매우 쉽다. Vcc핀은 우노의 5V핀에, GND핀은 우노의 그라운드 핀에 연결해 준다. 그리고 X, Y, Z 출력은 각각 우노의 A0, A1, A2에 연결해 주면 된다.

정확한 결과를 위해 아두이노 우노의 아날로그 레퍼런스 (AREF) 전압을 변경해 줄 필요가 있다. 여기서는 우노의 3.3V 핀을 우노의 AREF핀에 연결(아래 그림에서 빨간색 점선 부분)해 주면 된다.

  

예제 코드


const int xInput = A0;
const int yInput = A1;
const int zInput = A2;

// initialize minimum and maximum Raw Ranges for each axis
int RawMin = 0;
int RawMax = 1023;

// Take multiple samples to reduce noise
const int sampleSize = 10;

void setup() 
{
 analogReference(EXTERNAL);
 Serial.begin(115200);
}

void loop() 
{
 //Read raw values
 int xRaw = ReadAxis(xInput);
 int yRaw = ReadAxis(yInput);
 int zRaw = ReadAxis(zInput);

 // Convert raw values to 'milli-Gs"
 long xScaled = map(xRaw, RawMin, RawMax, -3000, 3000);
 long yScaled = map(yRaw, RawMin, RawMax, -3000, 3000);
 long zScaled = map(zRaw, RawMin, RawMax, -3000, 3000);

 // re-scale to fractional Gs
 float xAccel = xScaled / 1000.0;
 float yAccel = yScaled / 1000.0;
 float zAccel = zScaled / 1000.0;

 Serial.print("X, Y, Z  :: ");
 Serial.print(xRaw);
 Serial.print(", ");
 Serial.print(yRaw);
 Serial.print(", ");
 Serial.print(zRaw);
 Serial.print(" :: ");
 Serial.print(xAccel,0);
 Serial.print("G, ");
 Serial.print(yAccel,0);
 Serial.print("G, ");
 Serial.print(zAccel,0);
 Serial.println("G");

 delay(200);
}

// Take samples and return the average
int ReadAxis(int axisPin)
{
 long reading = 0;
 analogRead(axisPin);
 delay(1);
 for (int i = 0; i < sampleSize; i++)
 {
 reading += analogRead(axisPin);
 }
 return reading/sampleSize;
}

위의 코드에서는 가장 먼저 센서의 X,Y, Z 출력이 아두이노 우노의 어느 아날로그 입력에 연결되어 있는가를 선언해 준다.

다음은 아날로그입력의 최소/최대값을 지정해 준다. 아두이노 우노의 경우 10-bit ADC를 사용하므로 0~3.3V의 전압은 0~1023 사이의 값으로 변환된다.

sampleSize 변수는 더 정확한 결과를 얻기 위해 각 출력값에 몇개의 샘플값을 사용하는지를 지정한다.

* 주의 
analogReference(EXTERNAL)을 호출하는걸 빼먹으면 내부적으로 생성된 reference 전압과 AREF핀을 쇼트시키게 되므로 아두이노 우노를 망가트릴 수도 있으므로 가장 먼저 호출하는걸 잊어먹으면 안된다.

analogReference(EXTERNAL);
Serial.begin(115200);

loop() 함수에서 매 200ms마다 아날로그 출력값을 읽는다. analogRead() 함수 대신 ReadAxis() 함수를사용한다. 이 함수에서는 10개의 ADC 변환값을 읽어 평균값을 리턴해준다.

// Read raw values
int xRaw = ReadAxis(xInput);
int yRaw = ReadAxis(yInput);
int zRaw = ReadAxis(zInput);

ADXL335 출력을 가속도로 변환하기

센서의 아날로그 출력값을 실제 가속도로 변환해 줘야 한다. 여기서는 아두이노에 기본으로 들어 있는 map() 함수를 사용한다. map(xRaw, RawMin, RawMax, -3000, 3000); 를 호출하면 RawMin(0)은 -3000으로, RawMax(1023)는 3000으로, 0~1023 사이 값은 -3000~3000 사이에 비례하는 값으로 변환된다.

* 여기서 -3000, 3000은 임의의 값이 아니다. 이 값은 센서에서 측정된 실제 중력 가속도(1/1000 G 단위)를 나타낸다. 

  • 센서의 X축이 0V를 출력하면 (xRaw=0) map() 함수는 -3000을 리턴할 것이고 이는 즉 -3G를 나타낸다
  • 센서의 X축이 3.3V를 출력하면 (xRaw=1023) map() 함수는 3000을 리턴할 것이고 이는 즉 3G를 나타낸다.
  • 센서의 X축이 1.65V를 출력하면 (xRaw=511) map() 함수는 0을 리턴할 것이고 이는 즉 0G (가속도가 없음)을 나타낸다.

아래 그림은 모듈을 각각 다른 방향으로 놓았을 때 가속도 센서의 출력값을 보여준다.


ADXL335 셀프 테스트

ADXL335는 최종 어플리케이션에서 센서의 동작을 확인하기 위한 셀프테스트 기능을 가지고 있다.



모듈에 있는 ST(self-test)핀으로 이 기능을 제어한다.

ST핀이 3.3V에 연결되어 있으면 내부적으로 가속도계에 정전력이 가해진다. 이로 인해 사용자는 가속도 센서가 정상적으로 동작하는지 확인할 수 있다.

이로 인한 출력의 변화는 다음과 같다.

  • X축으로 -1.08g (-325mV)
  • Y축으로 +1.08g (+325mV)
  • Z축으로 +1.83g (+550mV)

정상동작시 ST핀은 오픈시켜 놓거나 GND에 연결해주면 된다.




















2019년 4월 19일 금요일

ESP32 딥슬립과 깨어나기 (ESP32 Deep Sleep & Wake-up source)


 

ESP32에서 deep sleep이 왜 필요할까?


어떤 상태에 있는가에 따라 ESP32는 상대적으로 전력소모가 큰 디바이스가 될 수도 있다. 일반적인 동작시 약 75mA 정도의 전류를 소비하지만 WiFi로 데이터를 전송하는 경우는 240mA 정도까지 소비하게 된다.

개발하는것이 외부 아답터를 사용하는 경우라면 전력소모에 대해 별로 신경 쓰지 않겠지만 배터리로 동작하는 경우라면 수 mA라도 크게 신경을 써야 한다. 이 문제의 해결책으로 deep sleep 모드롤 활용해 ESP32의 전력 사용을 줄여줄 수 있다.

ESP32의 다른 슬립모드에 대한 자세한 내용은 이전 포스트를 참조하면 된다.

ESP32 Deep Sleep

Deep Sleep 모드에서는 CPU, 대부분의 RAM과 모든 디지털 페리페럴의 전원이 꺼진다. 전원이 공급되는 부분은 RTC controller, RTC 페리페럴(ULP 코프로세서를 포함), RTC 메모리(slow & fast) 뿐이다.

ULP를 사용하는 경우 약 0.15mA, ULP도 꺼져있는 경우 약 10uA의 전류를 소비한다.



Deep Sleep mode동안 메인 CPU는 전원이 꺼져 있지만 ULP 코프로세서가 센서 값을 읽어 측정된 값에 따라 메인 시스템을 깨울 수 있다. 이런 sleep pattern은 ULP sensor-monitored pattern이라고 한다.

CPU뿐 아니고 칩의 메인 메모리도 전원이 꺼지기 때문에 메모리에 저장된 모든 내용은 사라지고 억세스 할 수 없다.

하지만 RTC 메모리는 전원이 유지되기 때문에 deep sleep mode에서도 내용이 유지되고 메인 프로세서가 깨어난 다음에 내용을 읽을 수 있다. 그렇기 때문에 ESP32가 WiFi와 블루투스를 끄기 전에 연결 데이터를 RTC 메모리에 저장하는 것이다.

그러므로 리부팅 후에 데이터를 사용하려면 전역변수를 선언할 때 RTC_DATA_ATTR attribute를 사용해 RTC 메모리에 저장해야 한다. 예를 들어 RTC_DATA_ATTR int bootCount = 0; 같이 해 주면 된다.

Deep Sleep mode에서는 RTC 모듈을 제외한 칩 전체의 전원이 꺼지기 때문에 RTC recovery memory에 들어 있지 않은 내용은 모두 사라지고 RESET으로 칩을 재시작한다. 즉 프로그램 실행은 다시 한번 맨 처음부터 다시 시작하게 된다는 의미이다.

* TIP
ESP32는 deep sleep에서 깨어날 때 deep sleep wake stub을 실행하는걸 지원한다. 이 함수는 칩이 깨어나자 마자 다른 어떤 코드 (정상적인 초기화 또는 부트로더 코드)보다 먼저 곧바로 실행된다. Wake stub 이 실행된 후에 칩은 sleep mode로 돌아가거나 또는 정상적으로 시작할 수 있다.

다른 sleep mode와 달리 시스템이 deep sleep mode로는 자동으로 들어갈 수 없다. Wake-up 소스를 설정한 후 esp_deep_sleep_start() 함수를 호출하면 곧바로 deep sleep mode로 들어가게 된다.

기본적으로 ESP32는 wake-up 소스에 필요 없는 모든 페리페럴의 전원을 자동으로 꺼 버리지만 옵션으로 모든 페리페럴들애 대해 전원을 끌지 여부를 결정할 수도 있다.


ESP32 Deep Sleep Wake-up Sources

여러가지 방법으로 Deep Sleep mode에서 깨어나게 할 수 있다.

  • Timer
  • Touch pad
  • External wakeup (ext0 & ext1)
Wake-up source는 여러개를 동시에 사용할 수도 있다. 이 경우 여러개의 소스 중 하나가 트리거되면 칩이 깨어나게 된다.

이 소스들은 sleep modㄷ로 들어가기 전에 아무때나 설정할 수 있다.

* 주의
아무 Wake-up source를 설정하지 않고 deep sleep 모드로 들어갈 수도 있는데, 이 경우 칩은 외부에서 리셋을 시켜주기 전에는 무한히 deep sleep mode에 있게 된다.

ESP32 Wake-up Source : Timer

RTC controller는 타이머를 내장하고 있어 정해진 시간 후에 칩을 깨울 수 있다.

시간은 us 단위로 지정하지만 실제 resolution은 선택된 클럭 소스에 따라 결정된다.

타이머를 사용하는 경우 esp_sleep_enable_timer_wakeup() 함수로 sleep wake-up을 활성화 시킬 수 있다.

다음은 타이머를 wake-up 소스로 사용하고 리부팅 될 때 마다 사용하기 위해 RTC 메모리에 데이터를 저장하는 가장 기본적인 예제이다.

#define uS_TO_S_FACTOR 1000000  //Conversion factor for micro seconds to seconds
#define TIME_TO_SLEEP  5        //Time ESP32 will go to sleep (in seconds)

RTC_DATA_ATTR int bootCount = 0;

void setup(){
 Serial.begin(115200);
 delay(1000); //Take some time to open up the Serial Monitor

 //Increment boot number and print it every reboot
 ++bootCount;
 Serial.println("Boot number: " + String(bootCount));

 //Print the wakeup reason for ESP32
 print_wakeup_reason();

 //Set timer to 5 seconds
 esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
 Serial.println("Setup ESP32 to sleep for every " + String(TIME_TO_SLEEP) +
 " Seconds");

 //Go to sleep now
 esp_deep_sleep_start();
}

void loop(){}

//Function that prints the reason by which ESP32 has been awaken from sleep
void print_wakeup_reason(){
 esp_sleep_wakeup_cause_t wakeup_reason;
 wakeup_reason = esp_sleep_get_wakeup_cause();
 switch(wakeup_reason)
 {
  case 1  : Serial.println("Wakeup caused by external signal using RTC_IO"); break;
  case 2  : Serial.println("Wakeup caused by external signal using RTC_CNTL"); break;
  case 3  : Serial.println("Wakeup caused by timer"); break;
  case 4  : Serial.println("Wakeup caused by touchpad"); break;
  case 5  : Serial.println("Wakeup caused by ULP program"); break;
  default : Serial.println("Wakeup was not caused by deep sleep"); break;
 }
}

ESP32 Wake-up Source : Touch Pad

RTC IO 모듈은 터치센서 인터럽트가 발생할 때 wake-up 시키는 회로를 가지고 있다.

칩이 deep sleep모드로 들어가기 전에 touch pad interrupt를 설정해 줘야 한다.

이 wake-up source를 활성화 하려면 esp_sleep_enable_touchpad_wakeup() 함수를 사용한다.

다음은 touch pad를 wake-up 소스로 사용하고 리부팅 될 때 마다 사용하기 위해 RTC 메모리에 데이터를 저장하는 가장 기본적인 예제이다.

//Define touch sensitivity. Greater the value, more the sensitivity.
#define Threshold 40

RTC_DATA_ATTR int bootCount = 0;
touch_pad_t touchPin;

void callback(){
  //placeholder callback function
}

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

  //Increment boot number and print it every reboot
  ++bootCount;
  Serial.println("Boot number: " + String(bootCount));

  //Print the wakeup reason for ESP32 and touchpad too
  print_wakeup_reason();
  print_wakeup_touchpad();

  //Setup interrupt on Touch Pad 3 (GPIO15)
  touchAttachInterrupt(T3, callback, Threshold);

  //Configure Touchpad as wakeup source
  esp_sleep_enable_touchpad_wakeup();

  //Go to sleep now
  esp_deep_sleep_start();
}

void loop(){}

//Function that prints the reason by which ESP32 has been awaken from sleep
void print_wakeup_reason(){
  esp_sleep_wakeup_cause_t wakeup_reason;
  wakeup_reason = esp_sleep_get_wakeup_cause();
  switch(wakeup_reason)
  {
    case 1  : Serial.println("Wakeup caused by external signal using RTC_IO"); break;
    case 2  : Serial.println("Wakeup caused by external signal using RTC_CNTL"); break;
    case 3  : Serial.println("Wakeup caused by timer"); break;
    case 4  : Serial.println("Wakeup caused by touchpad"); break;
    case 5  : Serial.println("Wakeup caused by ULP program"); break;
    default : Serial.println("Wakeup was not caused by deep sleep"); break;
  }
}


//Function that prints the touchpad by which ESP32 has been awaken from sleep
void print_wakeup_touchpad(){
  touch_pad_t pin;
  touchPin = esp_sleep_get_touchpad_wakeup_status();
  switch(touchPin)
  {
    case 0  : Serial.println("Touch detected on GPIO 4"); break;
    case 1  : Serial.println("Touch detected on GPIO 0"); break;
    case 2  : Serial.println("Touch detected on GPIO 2"); break;
    case 3  : Serial.println("Touch detected on GPIO 15"); break;
    case 4  : Serial.println("Touch detected on GPIO 13"); break;
    case 5  : Serial.println("Touch detected on GPIO 12"); break;
    case 6  : Serial.println("Touch detected on GPIO 14"); break;
    case 7  : Serial.println("Touch detected on GPIO 27"); break;
    case 8  : Serial.println("Touch detected on GPIO 33"); break;
    case 9  : Serial.println("Touch detected on GPIO 32"); break;
    default : Serial.println("Wakeup not by touchpad"); break;
  }
}

ESP32 Wake-up Source : External Wake-up

ESP32를 Deep Sleep에서 깨어나게 해 주는 두가지 타입의 외부 트리거가 있다.
  • ext0 - 특정 핀에 의해서만 칩을 wake-up 하고 싶을 때 사용
  • ext1 - 여러 버튼으로 wake-up 하고 싶을 때 사용

ext0 External Wake-up Source

RTC controller는 특정 핀이 미리 지정된 로직 레벨로 될 때 wake-up 시키는 회로를 가지고 있다. 여기에는 RTC GPIO 핀들 (0, 2, 4, 12~15, 25~27, 32~39) 중에 하나를 사용할 수 있다.

이 wake-up source를 활성화 시키려면 esp_sleep_enable_ext0_wakeup(GPIO_PIN, LOGIC_LEVEL) 함수를 사용한다. 이 함수는 두개의 파라미터가 필요하다. 첫번째는 어떤 핀을 사용할 것인가이고 두번째는 핀 상태가 어떨 때  (HIGH 또는 LOW) wake-up 시킬까를 결정한다.

ext0는 wake-up을 위해 RTC IO를 사용하기 때문에 이 wake-up source를 사용ㅎ면 deep sleep 동안에도 RTC 페리페럴에 전원이 공급된다.

이 모드에서 RTC IO 모듈이 활성화 되어 있으므로 내부 pull-up/down 역시 사용할 수 있다. 이 경우 esp_sleep_start()를 호출하기 전에 어플리케이션에서 rtc_gpio_pullup_en(), rtc_gpio_pulldown_en() 함수를 사용해 설정해 줘야 한다.

아래는 푸쉬버튼을 ext0 wake-up source로 동작시키기 위해 어떻게 연결했는가를 보여주는 회로도이다.



다음은 ext0를 wake-up source로 사용하는 가장 기초적인 예제 코드이다.

RTC_DATA_ATTR int bootCount = 0;

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

  //Increment boot number and print it every reboot
  ++bootCount;
  Serial.println("Boot number: " + String(bootCount));

  //Print the wakeup reason for ESP32
  print_wakeup_reason();

  //Configure GPIO33 as ext0 wake up source for HIGH logic level
  esp_sleep_enable_ext0_wakeup(GPIO_NUM_33,1);

  //Go to sleep now
  esp_deep_sleep_start();
}

void loop(){}

//Function that prints the reason by which ESP32 has been awaken from sleep
void print_wakeup_reason(){
  esp_sleep_wakeup_cause_t wakeup_reason;
  wakeup_reason = esp_sleep_get_wakeup_cause();
  switch(wakeup_reason)
  {
    case 1  : Serial.println("Wakeup caused by external signal using RTC_IO"); break;
    case 2  : Serial.println("Wakeup caused by external signal using RTC_CNTL"); break;
    case 3  : Serial.println("Wakeup caused by timer"); break;
    case 4  : Serial.println("Wakeup caused by touchpad"); break;
    case 5  : Serial.println("Wakeup caused by ULP program"); break;
    default : Serial.println("Wakeup was not caused by deep sleep"); break;
  }
}

ext1 External Wake-up Source

ESP32는 여러개의 GPIO 핀을 사용해 deep sleep mode에서 깨어나게 할 수도 있다. 이 경우 RTC GPIO핀들 (32~39)을 사용할 수 있다.

ext1 wake-up source는 RTC controller를 사용하기 때문에 RTC 페리페럴이나 RTC 메모리를 켜 줄 필요가 없다. 즉 내부 pull-up/down 저항을 사용할 수 없다.

내부 pull-up/down 저항을 사용하려면 deep sleep동안 RTC 페리페럴이 켜져 있도록 요청하고 sleep에 들어가기 전에 rtc_gpio_ 함수들을 사용해 pull-up/down 저항을 설정해 줘야 한다.

이 wake-up source를 활성화 시키려면 esp_sleep_enable_ext1_wakeup(BUTTON_PIN_MASK, LOGIC_LEVEL) 함수를 사용한다. 이 함수는 두개의 파라미터가 필요하다. 첫번째는 ESP32에게 어떤 핀들을 사용할지 알려주는 pin mask이다.

두번째는 핀 상태가 어떨 때  (HIGH 또는 LOW) wake-up 시킬까를 결정한다.

  • ESP_EXT1_WAKEUP_ANY_HIGH - 선택된 핀들 중 어느 하나가 HIGH일 때 wake-up
  • ESP_EXT1_WAKEUP_ANY_LOW - 선택된 핀들 중 어느 하나가 LOW일 때 wake-up
PIN MASK를 이해하는 가장 쉬운 방법은 바이너리 형태로 써 보는 것이다.




  • 0 - masked pin. 즉 이 핀은 외부 인터럽트 소스로 사용하지 않음
  • 1 - wake-up source로 활성화 된 핀
LSB가 GPIO0을 나타내고 MSB가 GPIO39를 나타낸다.

만일 어떤 GPIO핀도 wake-up source로 사용하고 싶지 않으면 모든 위치에 '0'을 써 넣으면 된다.

아래는 여러개의 푸쉬버튼을 ext1 wake-up source로 동작시키기 위해 어떻게 연결했는가를 보여주는 회로도이다.




다음은 ext1을 wake-up source로 사용하는 가장 기초적인 예제 코드이다.

//Pushbuttons connected to GPIO32 & GPIO33
#define BUTTON_PIN_BITMASK 0x300000000

RTC_DATA_ATTR int bootCount = 0;

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

  //Increment boot number and print it every reboot
  ++bootCount;
  Serial.println("Boot number: " + String(bootCount));

  //Print the wakeup reason for ESP32
  print_wakeup_reason();

  //Configure GPIO32 & GPIO33 as ext1 wake up source for HIGH logic level
  esp_sleep_enable_ext1_wakeup(BUTTON_PIN_BITMASK,ESP_EXT1_WAKEUP_ANY_HIGH);

  //Go to sleep now
  esp_deep_sleep_start();
}

void loop(){}

//Function that prints the reason by which ESP32 has been awaken from sleep
void print_wakeup_reason(){
  esp_sleep_wakeup_cause_t wakeup_reason;
  wakeup_reason = esp_sleep_get_wakeup_cause();
  switch(wakeup_reason)
  {
    case 1  : Serial.println("Wakeup caused by external signal using RTC_IO"); break;
    case 2  : Serial.println("Wakeup caused by external signal using RTC_CNTL"); break;
    case 3  : Serial.println("Wakeup caused by timer"); break;
    case 4  : Serial.println("Wakeup caused by touchpad"); break;
    case 5  : Serial.println("Wakeup caused by ULP program"); break;
    default : Serial.println("Wakeup was not caused by deep sleep"); break;
  }
}














ESP32의 슬립모드와 전력소모 (ESP32 Sleep modes and their power consumption)

Original document : Insight into ESP32 Sleep Modes & Their Power Consumption


Inside ESP32 chip

ESP32의 전력관리에 대해 이해하기 위해 먼저 ESP32 칩 내부의 구조를 알 필요가 있다. 아래 그림은 ESP32 칩 내부의 기능별 블록 다이어그램이다.



ESP32칩에는 듀얼코어 32-bit 마이크로프로세서와 448KB의 ROM, 520KB의 SRAM과 4MB의 플래쉬가 들어 있다.

또한 WiFI, Bluetooth, Cryptographic accelerator, RTC 및 다양한 페리페럴을 가지고 있다.

ESP32 Power Modes


ESP32의 전력 관리 모드로는 다음의 5가지가 있다.
  • Active mode
  • Modem Sleep mode
  • Light Sleep mode
  • Deep Sleep mode
  • Hibernation mode
각 모드별로 서로 다른 특징과 소비전력 절약 기능을 가지고 있다. 각 모드에 대해 하나씩 확인해 보자.

Active mode

일반적으로 사용하는 모드이다. 이 모드에서는 칩의 모든 기능이 활성화 된다.

액티브 모드에서는 칩의 모든 기능(WiFi, 프로세서 코어, 블루투스 등)을 켜 놓기 때문에 동작시 240mA 이상의 전류를 소모할 수 있다. 또한 WiFi와 블루투스를 동시에 사용하는 경우 순간적으로 최대 790mA까지 전류를 소모하는 경우도 있음을 주의해야 한다.



ESP32 데이터쉬트를 보면 액티브 모드에서 RF가 동작시 전력소모는 다음과 같다.



확실히 가장 많은 전류를 소비하는 비효율적인 모드이다. 그러므로 전력소모를 줄이고 싶으면 기능을 사용하지 않는 경우 적절한 다른 전력 모드로 스위칭 해 필요없는 기능을 비활성화시켜 줘야만 한다.

Modem Sleep mode

이 모드에서는 WiFi, 블루투스, RF를 제외한 모든 부분이 활성화 된다. 이 모드에서 slow speed라면 약 3mA, high speed라면 약 20mA 정도의 전류를 소비한다.



WiFI/블루투스 연결을 유지하려면 CPU, WiFi, 블루투스, RF가 Association Sleep pattern이라는 미리 지정된 간격으로 깨어나야 한다.

이 sleep pattern동안 전원 모드는 active mode와 modem sleep mode를 왔다갔다 해야 한다.

ESP32가 스테이션 모드에서 공유기에 연결되었을 때만 modem sleep 모드에 들어갈 수 있다. 이 경우 DTIM beacon mechanism을 통해 공유기 연결을 유지한다.

전력 소비를 줄이기 위해 ESP32는 DTIM beacon interval 사이에는 WiFi 모듈을 비활성화 시키고 다음번 Beacon interval이 돌아오기 전에 자동으로 깨어나게 한다.

Sleep time은 공유기의 DTIM Beacon interval time에 의해 결정되는되 일반적으로 100ms ~ 1000ms 정도 된다.

Light Sleep mode

 Light sleep mode는 modem sleep mode와 비슷하고 association sleep pattern도 따른다. 차이점은 디지털 페리페럴, 대부분의 RAM, CPU가 clock-gated가 된다.


Light Sleep mode동안 CPU는 클럭펄스를 꺼 버림으로서 pause되지만 RTC와 ULP 코프로세서는 활성화 되어 있다. 그러므로 modem sleep mode보다도 더 전력소모를 줄여 약 0.8mA 정도의 전류를 소모한다.



Light Sleep mode로 들어가기 전에 ESP32는 내부상태를 저장하고 sleep에서 깨어나면 동작을 재개하는데 이를 Full RAM Retention이라 한다.

Wake-up 소스를 설정한 후 esp_light_sleep_start() 함수를 사용해 light sleep mode로 들어갈 수 있다.

Deep Sleep mode

Deep Sleep mode에서는 CPU, 대부분의 RAM과 모든 디지털 페리페럴이 꺼진다. 전원이 켜져 있는 부분은 RTC 컨트롤러, RTC 페리페럴(ULP 코프로세서를 포함), RTC 메모리 (slow & fast) 뿐이다.

이 경우 ULP 코프로세서가 켜져 있는 경우 약 0.15mA, 그렇지 않은 경우 10uA 정도의 전류만을 소비한다.



Deep Sleep mode동안 메인 CPU는 전원이 꺼져 있지만 ULP 코프로세서가 센서 값을 읽어 측정된 값에 따라 메인 시스템을 깨울 수 있다. 이런 sleep pattern은 ULP sensor-monitored pattern이라고 한다.

CPU뿐 아니고 칩의 메인 메모리도 전원이 꺼지기 때문에 메모리에 저장된 모든 내용은 사라지고 억세스 할 수 없다.

하지만 RTC 메모리는 전원이 유지되기 때문에 deep sleep mode에서도 내용이 유지되고 메인 프로세서가 깨어난 다음에 내용을 읽을 수 있다. 그렇기 때문에 ESP32가 WiFi와 블루투스를 끄기 전에 연결 데이터를 RTC 메모리에 저장하는 것이다.

그러므로 리부팅 후에 데이터를 사용하려면 전역변수를 선언할 때 RTC_DATA_ATTR attribute를 사용해 RTC 메모리에 저장해야 한다. 예를 들어 RTC_DATA_ATTR int bootCount = 0; 같이 해 주면 된다.

Deep Sleep mode에서는 RTC 모듈을 제외한 칩 전체의 전원이 꺼지기 때문에 RTC recovery memory에 들어 있지 않은 내용은 모두 사라지고 RESET으로 칩을 재시작한다. 즉 프로그램 실행은 다시 한번 맨 처음부터 다시 시작하게 된다는 의미이다.

* TIP
ESP32는 deep sleep에서 깨어날 때 deep sleep wake stub을 실행하는걸 지원한다. 이 함수는 칩이 깨어나자 마자 다른 어떤 코드 (정상적인 초기화 또는 부트로더 코드)보다 먼저 곧바로 실행된다. Wake stub 이 실행된 후에 칩은 sleep mode로 돌아가거나 또는 정상적으로 시작할 수 있다.

다른 sleep mode와 달리 시스템이 deep sleep mode로는 자동으로 들어갈 수 없다. Wake-up 소스를 설정한 후 esp_deep_sleep_start() 함수를 호출하면 곧바로 deep sleep mode로 들어가게 된다.

기본적으로 ESP32는 wake-up 소스에 필요 없는 모든 페리페럴의 전원을 자동으로 꺼 버리지만 옵션으로 모든 페리페럴들애 대해 전원을 끌지 여부를 결정할 수도 있다.

Hibernation mode

Deep Sleep mode와 다르게 Hibernation mode는 내부 8MHz 오실레이터와 ULP 코프로세서까지도 전원을 꺼 버린다. RTC recovery 메모리까지도 전원을 꺼 버리기 때문에 hibernation mode에서는 어떤 데이터도 보관할 수 없다.

Slow clock상의 RTC timer와 일부 RTC GPIO만 활성화 되어 있고 나머지는 모두 전원이 꺼진다. 활성화 된 부분이 hibernation mode에서 깨어나는걸 담당한다.

이 모드는 전력소비를 극단적으로 줄여 약 2.5uA의 전류를 소비한다.














2019년 4월 12일 금요일

ESP32에서 멀티코어 사용하기 (Use multicore on ESP32)

ESP32는 2개의 Xtensa 32-bit LX6 마이크로 프로세서 코어를 가지고 있다. 아두이노 IDE에서 코드를 실행하면 디폴토로 코어 1에서 실행된다. 여기서는 태스크를 만들어 두번째 코어에서 코드를 실행하는 방법을 설명한다. 그렇게 하면 두개의 코어에서 동시에 코드를 실행해서 멀티태스킹을 할 수 있다.

코드가 어느 코어에서 실행되고 있는가를 확인하려면 xPortGetCoreID() 함수를 사용하면 된다.

아래의 간단한 테스크 코드를 실행시켜 보면 setup(), loop() 함수 모두 코어 1에서 실행되는걸 확인할 수 있다.

void setup()
{
  Serial.begin(115200);
  Serial.print("* setup() is running on core ");
  Serial.println(xPortGetCoreID());
}

void loop()
{
  Serial.print("* loop() is running on core ");
  Serial.println(xPortGetCoreID());
  delay(100);
}




ESP32를 아두이노 IDE에서 사용할 때 기본적으로 리얼타임 OS인 FreeRTOS를 지원한다. 그러므로 FreeRTOS를 사용하면 여러개의 태스크를 독립적으로 병렬로 실행할 수 있다.

태스크는 무언가를 수행하는 코드의 조각이다. 예를 들어 LED를 깜빡이거나 네트웍 통신, 센서 읽기, 센서값을 네트워그로 전송등 다양한 작업을 수행할 수 있다.

코드의 특정 부분이 특정 코어에서 실행되도록 할당하려면 태스크를 만들어 줘야 한다. 태스크를 만들 때 우선순위 뿐 아니고 어느 코어에서 실행할지 고를 수 있다. 우선순위는 0에서 시작하고 0이 가장 우선순위가 낮다. 프로세서는 우선순위가 높은 태스크를 먼저 실행한다.





1. 태스크를 만들려면 먼저 태스크 핸들을 만들어 준다.

TaskHandle_t Task1;

2. setup()에서 xTaskCreatePinnedToCore 함수를 사용해 특정 코어에 고정된 태스크를 만들어 준다.

xTaskCreatePinnedToCore (
  Task1code,      // 태스크를 구현한 함수
  “Task1”,        // 태스크 이름
  10000,          // 스택 크기 (word단위)
  NULL,           // 태스크 파라미터
  0,              // 태스크 우선순위
  &Task1,         // 태스크 핸들
  0 );            // 태스크가 실행될 코어


3. 만들어 진 태스크가 실행할 코드가 들어있는 함수를 만들어 줘야만 한다. 위의 예제에서는 Task1Code 함수를 만들어 줘야 한다.

void Task1Code( void *param)
{
  while (1) {
    // 태스크 1이 실행할 코드가 들어갈 부분
    // 무한루프로 실행됨
  }
}


코드가 실행되는 도중 만들어 진 태스크를 삭제하고 싶으면 vTaskDelete() 함수를 사용하면 된다. 이 함수의 파리미터로 지우고자 하는 태스크의 핸들(위의 예제에서는 Task1)을 넘겨준다.

예제


ESP32의 2번핀에 LED1을, 4번핀에 LED2를 연결하고 두개의 서로 다른 태스크가 각각 LED1은 1000ms마다, LED2는 700ms마다 깜빡이게 만든다. 여기서 Task1은 코어 0에서, Task2는 코어 1에서 실행된다.




#define LED1 2
#define LED2 4

TaskHandle_t Task1;
TaskHandle_t Task2;

void setup()
{
  Serial.begin(115200);
  pinMode(LED1, OUTPUT);
  pinMode(LED2, OUTPUT);

  xTaskCreatePinnedToCore(
    blink1000,         // 태스크 함수
    "Task1",           // 테스크 이름
    10000,             // 스택 크기(워드단위)
    NULL,              // 태스크 파라미터
    1,                 // 태스크 우선순위
    &Task1,            // 태스크 핸들
    0);                // 실행될 코어

  xTaskCreatePinnedToCore(
    blink700,          // 태스크 함수
    "Task2",           // 테스크 이름
    10000,             // 스택 크기(워드단위)
    NULL,              // 태스크 파라미터
    1,                 // 태스크 우선순위
    &Task2,            // 태스크 핸들
    1);                // 실행될 코어
}

void blink1000 ( void *param )
{
  Serial.print("# Task 1 running on core ");
  Serial.println(xPortGetCoreID());

  while (1) {
    digitalWrite(LED1, HIGH);
    delay(1000);
    digitalWrite(LED1, LOW);
    delay(1000);
  }
}

void blink700 ( void *param )
{
  Serial.print("# Task 2 running on core ");
  Serial.println(xPortGetCoreID());

  while (1) {
    digitalWrite(LED2, HIGH);
    delay(700);
    digitalWrite(LED2, LOW);
    delay(700);
  }
}

void loop()
{
}






2018년 11월 20일 화요일

ESP32 PWM 사용하기

ESP32에는 16개의 독립적인 LED PWM 채널을 가지고 있어 각각 다른 속성의 PWM 신호를 만들어 낼 수 있다.

1. 어떤 PWM 채널을 사용할 지 골라야 한다. 0~15 사이의 값을 사용할 수 있다.
2. PWM 주파수를 설정해 줘야 한다. LED의 경우 5000 Hz 정도면 충분하다.
3. PWM duty cycle resolution을 설정해 줘야 한다. 해상도(resolution)은 1비트에서 16비트까지 지정할 수 있다. 여기서는 8 bit 해상도를 사용하는데 그 경우 LED 밝기 값은 0에서 255가 될 수 있다. 만일 16 bit로 설정하면 좀 더 세밀하게 (0~65535) 밝기 값을 조정할 수 있게 된다.

ledcSetup(ch, freq, resolution);   // ch: PWM channel (0~15), freq: PWM 주파수, resolution: PWM  해상도


PWM 주파수는 1초에 몇번 신호를 ON/OFF 할 것인지 결정한다. 위의 그림에서 처럼 PWM 주파수를 4 Hz로 한다면 CH0에 연결되어 있는 LED는 4번 on/off를 반복할 것이다. 8 Hz인 경우 CH1에 연결되어 있는 LED는 8번 on/off를 하게 될 것이다. 아두이노 우노의 경우 PWM 주파수는490 Hz(5,6번 핀은 980 Hz)로 고정되어 있다.

Resolution은 한 주기 내에서 얼마나 세밀하게 시간을 지정할 수 있는가를 결정한다. 8-bit resolution인 경우 2^8 = 256 등분으로 나누지만 10-bit resolution이 되면 2^10 = 1024 등분으로 나뉘어 훨씬 더 정밀하게 제어가 가능해진다. ESP32에서는 1-bit 부터 16-bit까지 resolution을 지정할 수 있다. 아두이노 우노의 경우에는 analogWrite()의 resolution은 8-bit로 고정되어 있어 출력값으로 0~255 사이의 값을 사용해야 한다.

4. 설정한 채널의 출력을 어느 GPIO 핀으로 보낼것인지 지정해야 한다.

ledcAttachPin(gpio, ch);   // gpio : GPIO 핀 번호,  ch : PWM channel

5. 이제 ledcWrite() 함수를 사용해 LED 밝기를 조절할 수 있다.

ledcWrite(ch, duty); // ch : PWM channel, duty : Duty cycle

duty 값은 한 주기 내에서 ON 시간과 OFF 시간의 비율을 결정하는 값이다.


위의 그림을 보면 알 수 있듯이 PWM 주파수가 다르면 같은 duty 값이라도 ON 되어 있는 시간이 달라진다. 다만 전체적으로 ON 시간의 합과 OFF 시간의 합의 비율은 동일하다.




위와 같이 LED를 연결한 경우 LED가 점점 밝아졌다가 다시 점점 어두워지는걸 반복하는 코드는 다음과 같다.

const int ledPin = 16;  // 16 corresponds to GPIO16
// setting PWM properties
const int freq = 5000;
const int ledChannel = 0;
const int resolution = 8;
 

void setup(){
  ledcSetup(ledChannel, freq, resolution);  
// configure LED PWM functionalitites  
  ledcAttachPin(ledPin, ledChannel);  
  // attach the channel to the GPIO to be controlled
}
 void loop(){
  // increase the LED brightness
  for(int dutyCycle = 0; dutyCycle <= 255; dutyCycle++){   
    // changing the LED brightness with PWM
    ledcWrite(ledChannel, dutyCycle);
    delay(15);
  }

  // decrease the LED brightness
  for(int dutyCycle = 255; dutyCycle >= 0; dutyCycle--){
    // changing the LED brightness with PWM
    ledcWrite(ledChannel, dutyCycle);   
    delay(15);
  }
}

PWM 신호의 frequency를 조정할 수 있으므로 16개의 서보모터를 동시에 제어할 수도 있다.


서보모터는 위와 같은 PWM 신호로 제어를 하기 때문에 PWM frequency를 50 Hz로 해 주고 16-bit resolution을 사용하는 경우 duty값 3277이 0도, 4915가 90도, 6554가 180도가 된다.

즉 GPIO16에 연결된 서보모터를 CH0를 사용해서 제어한다고 하면 다음과 같이 할 수 있다.

void setup()
{
  ...
  ledcSetup(0, 50, 16);    // PWM CH 0, Freq. 50 Hz, 16-bit resolution
  ledcAttach(16, 0);         // PWM CH 0을 GPIO 16번으로 출력
  ...
}

// deg는 0~180도 까지
void servoWrite(int ch, int deg)
{
  int duty = deg*18.2 + 3277;

  ledcWrite(ch, duty);
}

void loop()
{
  ...
  servoWrite(0, 90);   // CH 0에 연결된 서보를 90도로
  ...
}







2018년 11월 16일 금요일

ESP32에서 NTP(Network Time Protocol) 서버로부터 시간 가져오기



인터넷에서 정확한 시간을 알기 위해 사용하는 프로토콜로 NTP(Network Time Protocol)이 있다. 곳곳에 운영중인 NTP서버에 접속해 정확한 현재 날짜와 시간을 가져 올 수 있다.



ESP32에서도 NTP를 사용할 수 있게 이미 NTP client 라이브러리가 공개되어 있다.

Download NTP Client Library

위의 링크를 클릭하면 라이브러리를 다운받아 설치해주면 된다.

코드 구조는 다음과 같다.

1. WiFi.h, WiFiUdp.h, NTPClient.h 를 include 해 줘야 한다.

#include <WiFi.h>
#include <WiFiUdp.h>
#include <NTPClient.h>

2. 인터넷 연결을 위해 공유기 설정을 해 준다.

const char* ssid  = "XXXXX";
const char* password = "YYYYY";

위에서 XXXXX 대신 사용할 공유기의 이름(SSID)로, YYYYY 대신 공유기의 암호로 바꿔줘야 한다.

3. 이제 NTP client를 만들어 준다.

WiFiUdp ntpUDP;
NTPClient timeClient(ntpUdp);

4. 공유기에 접속한 후 NTP client를 시작한다.

....
timeClient.begin();

5. 자신의 timezone에 맞게 시간을 조정하기 위해 setTimeOffset() 메소드를 사용할 수 있는데 이때 offset은 초 단위로 지정해 줘야 한다. 즉 1시간은 3600초이므로 한국의 경우 GMT+9 이니까 3600*9 = 32400이 된다.

timeClient.setTimeOffset(32400);

6. NTP 서버에 요청해 시간을 가져온다.

while (!timeClient.update()) {
  timeClient.forceUpdate();
}

7. NTP 서버에게서 읽어온 값을 사람이 볼 수 있는 형태로 변환해준다.

formattedDate = timeClient.getFormattedDate();

getFormattedDate()가 리턴한 값은 다음과 같은 포맷이다.

2018-11-09T07:34:21z

T 앞쪽이 날짜가 되고 T에서 z까지가 시간이므로 각각을 기준으로 원하는 내용을 추출해서 출력해주면 된다.

실행 가능한 전체 코드는 다음과 같다.

#include <WiFi.h>
#include <WiFiUdp.h>

#include <NTPClient.h>

const char* ssid     = "XXXXX";  // XXXXX 를 접속할 공유기 SSID로 변경
const char* password = "YYYYY"   // YYYYY 를 접속할 공유기 암호로 변경

// Define NTP Client to get time
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP);

// Variables to save date and time
String formattedDate;
String dayStamp;
String timeStamp;

void setup() {
  Serial.begin(115200);
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected.");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());

  timeClient.begin();    // NTP 클라이언트 초기화
  // 자신의 timezone에 맞게 초 단위로 time offset을 설정해준다. 예를 들어
  // GMT +1 = 3600
  // GMT +8 = 28800
  // GMT -1 = -3600
  // GMT 0 = 0
  timeClient.setTimeOffset(32400);  // 한국은 GMT+9이므로 9*3600=32400
}
void loop() {
  while(!timeClient.update()) {
    timeClient.forceUpdate();
  }
  // formattedDate 은 다음과 같은 형태임
  // 2018-11-12T16:00:13Z
  formattedDate = timeClient.getFormattedDate();
  Serial.println(formattedDate);

  // 날짜 추출
  int splitT = formattedDate.indexOf("T");
  dayStamp = formattedDate.substring(0, splitT);
  Serial.print("DATE: ");
  Serial.println(dayStamp);
  // 시간 추출
  timeStamp = formattedDate.substring(splitT+1, formattedDate.length()-1);
  Serial.print("HOUR: ");
  Serial.println(timeStamp);
  delay(1000);
}


코드의 실행 결과이다.

Connecting to XXXXX
.......
WiFi connected.
IP address:
172.20.10.13
2018-11-09T08:53:26Z
DATE: 2018-11-09
HOUR: 08:53:26
2018-11-09T08:53:27Z
DATE: 2018-11-09
HOUR: 08:53:27
2018-11-09T08:53:28Z
DATE: 2018-11-09
HOUR: 08:53:28


















2018년 11월 9일 금요일

ESP32에서 터치센서 사용하기 (Using ESP32 Capacitive Touch Sensor)



이전 포스트(ESP32 GPIO 레퍼런스)에서 소개했던 것 처럼 ESP32에는 총 10개의 터치 센서가 들어 있다. 이를 사용하면 아무런 추가 하드웨어 없이 터치 스위치를 만들 수 있다. 10개 터치 센서의 핀 매핑은 다음과 같다.

static const uint8_t T0 = 4;
static const uint8_t T1 = 0;
static const uint8_t T2 = 2;
static const uint8_t T3 = 15;
static const uint8_t T4 = 13;
static const uint8_t T5 = 12;
static const uint8_t T6 = 14;
static const uint8_t T7 = 27;
static const uint8_t T8 = 33;
static const uint8_t T9 = 32;

사용법 역시 매우 간단하다. 푸쉬버튼을 사용하는 경우 먼저 setup()에서 스위치가 연결된 핀을 pinMode()로 설정을 해 주고 digitalRead()로 상태를 읽어야 하는데 터치의 경우 별도의 설정은 필요 없고 touchRead() 함수로 값을 읽으면 된다.

아래 예제 코드는 T0 (4번핀)를 터치 스위치로 사용해서 LED를 토글시키는 것이다.


#define TOUCH_SW T0    // connected to 4
#define LED A13        // connected to 15

int touchVal = 100;

void setup()
{
  Serial.begin(115200);
  Serial.println("ESP32 Touch Example");
  pinMode(LED, OUTPUT);
}

void loop()
{
  static boolean led_state = LOW;
  static boolean prevState = LOW; 
  static boolean touchState;

  touchVal = touchRead(TOUCH_SW);     // read touch switch state
  touchState = touchVal < 50 ? HIGH:LOW;
  if ((LOW == prevState) && (HIGH == touchState))  {    
    Serial.println("Switch touched");
    led_state = !led_state;
    digitalWrite(LED, led_state);      
  }
  prevState = touchState;
  delay(50);
}