ソースに絡まるエスカルゴ

貧弱プログラマの外部記憶装置です。

【ESP32】analogReadする方法

 ESP32でanalogReadをしたことがないことに気づいたので、調べてやってみました。ついでに照度センサーの値の読み取りも行いました。


1:ESP32でanalogReadする方法
 結論から言うと、以下のようにArduinoUnoなどの時と同じような記述でできます。

int PIN_NUM = 25;
int reading = analogRead(PIN_NUM);

 ただし読み取り値は0〜1023ではなく0〜4096で、入力電圧の範囲は0〜3.3Vのようです。

 またどのピンでもanalogReadできるわけではないらしく、ざっと試してみたところ以下の図の「ADCと記述のあるピンのみanalogReadできる」ようです。
(※ちゃんと検証したわけではないので、間違っている可能性もあります)
f:id:rikoubou:20170629133746p:plain

 以上のいくつか注意点はありますが、ESP32でもanalogReadができることがわかりました。


2:ESP32で照度センサーを使ってみる
フォトICダイオードS9648(5個入): センサ一般 秋月電子通商 電子部品 ネット通販

 この照度センサーの値をanalogReadで読み込みます。プログラムは以下の通りです。

・ESP32_photoIC

/**
 * 繋ぎ方:http://arms22.blog91.fc2.com/blog-entry-416.html
 * データシート:http://akizukidenshi.com/download/s9648.pdf
 * 
 * カソード側(足が短い方)に電源を、アノード側(足が長い方)に読み取りピンを刺す
 * ※普通のLEDとは違うので注意!!
 */

const int voutPin = 25;
const int VOLT = 3.3; // 3.3Vを電源とした場合
const int ANALOG_MAX = 4096; // ESP32の場合

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

void loop() {
  // R1の電圧を取得
  int reading = analogRead(voutPin);

  // AD値をmVに変換
  float voltage = ((long)reading * VOLT * 1000) / ANALOG_MAX;

  Serial.print(voltage);
  Serial.print(" mV, ");

  // 電圧から電流を求める I=E/R (R=1000)
  float microamp = (voltage * 1000) / 1000;

  Serial.print(microamp);
  Serial.print(" uA, ");

  // 電流をlxに変換
  float lx = microamp / (290 / 100);

  Serial.print(lx);
  Serial.println(" lx");

  delay(1000);
}

 回路図は参考記事の「Arduinoで遊ぼう - フォトICダイオード S9648-100 を使って明るさを求める」の回路を参考にして繋いでください。

 シリアルモニタを立ち上げると1秒ごとに読み取った値が表示されます。センサーの上を手で覆うなりすると数値が低く変動するはずです。


 以上、ESP32でanalogReadする方法でした。


・参考記事
Arduino 言語における ESP-WROOM-32 の入出力関数 | なんかしてるえかいくん
Arduinoで遊ぼう - フォトICダイオード S9648-100 を使って明るさを求める - なんでも作っちゃう、かも。

【ESP32】NEC方式で赤外線通信できるプログラムを作ってみた

rikoubou.hatenablog.com

 ↑前にESP32で赤外線通信を行う記事を書きましたが、実用性がありませんでした。

 今回かなり苦しみましたが、ESP32同士でNEC方式での赤外線通信に成功したので備忘録もかねて記事にしておきます。

 まだESP32で赤外線のライブラリがないようなので、困っている人の助けになれば幸いです。


1:材料
 前回の記事と同じものを使います。赤外線LEDは940nmのものであれば、どれでも大丈夫だと思います。

・赤外線LED
3φ砲弾型赤外線LED 940nm LIR034の通販ならマルツオンライン

・赤外線受光器
リモコン受光モジュール RPM7138-Rの通販ならマルツオンライン

・適当な単色LED
・ESP32-DevKitC×2
・単3電池ボックス(必要に応じて)
・200Ω程度の抵抗×2
・ブレッドボード×2


2:受信側について
・ESP32_IRrecieve.h

/**
 * 参考URL:https://github.com/espressif/esp-idf/blob/master/examples/peripherals/rmt_nec_tx_rx/main/infrared_nec_main.c
 */
#ifdef __cplusplus
extern "C" {
#endif

#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "freertos/ringbuf.h"

#include "esp32-hal.h"
#include "esp_intr.h"
#include "esp_err.h"
#include "esp_log.h"
#include "driver/gpio.h"
#include "driver/rmt.h"
#include "driver/periph_ctrl.h"
#include "soc/rmt_reg.h"

#ifdef __cplusplus
}
#endif

#define RMT_RX_CHANNEL   RMT_CHANNEL_0                     /*!< RMT channel for receiver */
#define RMT_CLK_DIV      100                               /*!< RMT counter clock divider */
#define RMT_TICK_10_US    (80000000/RMT_CLK_DIV/100000)    /*!< RMT counter value for 10 us.(Source clock is APB clock) */

#define rmt_item32_tIMEOUT_US  9500                        /*!< RMT receiver timeout value(us) */

#define NEC_DATA_ITEM_NUM       32                         /*!< NEC code item number: 32bit data */
#define NEC_HEADER_HIGH_US    9050                         /*!< NEC protocol header: positive 9ms */
#define NEC_HEADER_LOW_US     4510                         /*!< NEC protocol header: negative 4.5ms*/
#define NEC_BIT_ONE_HIGH_US    560                         /*!< NEC protocol data bit 1: positive 0.56ms */
#define NEC_BIT_ONE_LOW_US    (2250-NEC_BIT_ONE_HIGH_US)   /*!< NEC protocol data bit 1: negative 1.69ms */
#define NEC_BIT_ZERO_HIGH_US   560                         /*!< NEC protocol data bit 0: positive 0.56ms */
#define NEC_BIT_ZERO_LOW_US   (1120-NEC_BIT_ZERO_HIGH_US)  /*!< NEC protocol data bit 0: negative 0.56ms */
#define NEC_BIT_MARGIN         100                         /*!< NEC parse margin time */

#define NEC_ITEM_DURATION(d)  ((d & 0x7fff)*10/RMT_TICK_10_US)   /*!< Parse duration time from memory register value */

class ESP32_IRrecieve {
  public:
    uint16_t address1;
    uint16_t address2;
    uint16_t command1;
    uint16_t command2;

    ESP32_IRrecieve(int recievePin);
    void irRecieve();
    void printIRData();

  private:
    bool parseItems(rmt_item32_t* item, int item_num);
    bool headerCheck(rmt_item32_t* item);
    bool bit1Check(rmt_item32_t* item);
    bool bit0Check(rmt_item32_t* item);
    bool inRangeCheck(int duration_ticks, int target_us, int margin_us);
};


・ESP32_IRrecieve.cpp

/**
 * 参考URL:https://github.com/espressif/esp-idf/blob/master/examples/peripherals/rmt_nec_tx_rx/main/infrared_nec_main.c
 */
#include <Arduino.h>
#include "ESP32_IRrecieve.h"

RingbufHandle_t rb = NULL;

/*
 * 初期化関数
 */
ESP32_IRrecieve::ESP32_IRrecieve(int recievePin) {  
  rmt_config_t rmt_rx;
  rmt_rx.channel = RMT_RX_CHANNEL;
  rmt_rx.gpio_num = (gpio_num_t) recievePin; // 赤外線信号を受け取るピンを設定
  rmt_rx.clk_div = RMT_CLK_DIV;
  rmt_rx.mem_block_num = 1;
  rmt_rx.rmt_mode = RMT_MODE_RX;
  rmt_rx.rx_config.filter_en = true;
  rmt_rx.rx_config.filter_ticks_thresh = 100;
  rmt_rx.rx_config.idle_threshold = rmt_item32_tIMEOUT_US / 10 * (RMT_TICK_10_US);
  rmt_config(&rmt_rx);
  rmt_driver_install(rmt_rx.channel, 1000, 0);

  rmt_get_ringbuf_handler(rmt_rx.channel, &rb);
  rmt_rx_start(rmt_rx.channel, 1);
}

/**
 * 赤外線を受信する関数
 */
void ESP32_IRrecieve::irRecieve() {
  while(rb) {
    size_t rx_size = 0;
    rmt_item32_t* item = (rmt_item32_t*) xRingbufferReceive(rb, &rx_size, 1000); // 受信待ち
    if (item) {
      bool res = parseItems(item, rx_size / 4); // 赤外線信号をパース
      if(res) {
        printIRData(); // 取得した赤外線信号を表示
        vRingbufferReturnItem(rb, (void*) item); // bufferをクリア
      } else {
        vRingbufferReturnItem(rb, (void*) item); // bufferをクリア
        break;
      }
    }
  }
}

/**
 * 最後に取得した赤外線の情報をprintする関数
 */
void ESP32_IRrecieve::printIRData() {
  Serial.print("address1:");
  Serial.print(address1, HEX);
  Serial.print(" address2:");
  Serial.print(address2, HEX);
  Serial.print(" command1:");
  Serial.print(command1, HEX);
  Serial.print(" command2:");
  Serial.println(command2, HEX);
}

/**
 * 受信した赤外線信号をパースする関数
 */
bool ESP32_IRrecieve::parseItems(rmt_item32_t* item, int item_num) {
  // 赤外線信号のサイズを判定
  if(item_num < NEC_DATA_ITEM_NUM) {
    Serial.print("Header Size Error! :");
    Serial.println(item_num);
    return false;
  }

  // 赤外線信号のヘッダーを判定
  if(!headerCheck(item++)) {
    Serial.println("headerCheck Error!");
    return false;
  }

  uint16_t add1 = 0, add2 = 0, cmd1 = 0, cmd2 = 0;

  // 最初の8bit(address1)を取り出す
  for (int j = 0; j < 8; j++) {
    if(bit1Check(item)) {
      add1 |= (1 << j);
    } else if(bit0Check(item)) {
      add1 |= (0 << j);
    } else {
      Serial.println("First 8bit Error!");
      return false;
    }
    item++;
  }

  // 次の8bit(address2)を取り出す
  for (int j = 0; j < 8; j++) {
    if(bit1Check(item)) {
      add2 |= (1 << j);
    } else if(bit0Check(item)) {
      add2 |= (0 << j);
    } else {
      Serial.println("Second 8bit Error!");
      return false;
    }
    item++;
  }

  // 次の8bit(command1)を取り出す
  for(int j = 0; j < 8; j++) {
    if(bit1Check(item)) {
      cmd1 |= (1 << j);
    } else if(bit0Check(item)) {
      cmd1 |= (0 << j);
    } else {
      Serial.println("Third 8bit Error!");
      return false;
    }
    item++;
  }

  // 最後の8bit(command2)を取り出す
  for(int j = 0; j < 8; j++) {
    if(bit1Check(item)) {
      cmd2 |= (1 << j);
    } else if(bit0Check(item)) {
      cmd2 |= (0 << j);
    } else {
      Serial.println("Last 8bit Error!");
      return false;
    }
    item++;
  }

  // 全て正常に取得できた場合は値を設定
  address1 = add1;
  address2 = add2;
  command1 = cmd1;
  command2 = cmd2;

  return true;
}

/**
 * 赤外線信号のヘッダー部分を判定する関数
 */
bool ESP32_IRrecieve::headerCheck(rmt_item32_t* item) {
  if(inRangeCheck(item->duration0, NEC_HEADER_HIGH_US, NEC_BIT_MARGIN)
      && inRangeCheck(item->duration1, NEC_HEADER_LOW_US, NEC_BIT_MARGIN)) {
    return true;
  }
  return false;
}

/**
 * 赤外線信号の1を判定する関数
 */
bool ESP32_IRrecieve::bit1Check(rmt_item32_t* item) {
  if(inRangeCheck(item->duration0, NEC_BIT_ONE_HIGH_US, NEC_BIT_MARGIN)
      && inRangeCheck(item->duration1, NEC_BIT_ONE_LOW_US, NEC_BIT_MARGIN)) {
    return true;
  }
  return false;
}

/**
 * 赤外線信号の0を判定する関数
 */
bool ESP32_IRrecieve::bit0Check(rmt_item32_t* item) {
  if(inRangeCheck(item->duration0, NEC_BIT_ZERO_HIGH_US, NEC_BIT_MARGIN)
      && inRangeCheck(item->duration1, NEC_BIT_ZERO_LOW_US, NEC_BIT_MARGIN)) {
    return true;
  }
  return false;
}

/**
 * 赤外線信号の範囲を判定する関数
 */
bool ESP32_IRrecieve::inRangeCheck(int duration_ticks, int target_us, int margin_us) {
//  Serial.println(NEC_ITEM_DURATION(duration_ticks));
  if((NEC_ITEM_DURATION(duration_ticks) < (target_us + margin_us))
      && ( NEC_ITEM_DURATION(duration_ticks) > (target_us - margin_us))) {
      return true;
  } else {
      return false;
  }
}


・ESP32_IRrecieveTest.ino

#include "ESP32_IRrecieve.h"

const int RECV_PIN = 25; // 赤外線情報を読み取るピン

ESP32_IRrecieve irObj(RECV_PIN);

void setup() {
  Serial.begin(115200);
  Serial.println("Init complete");
  xTaskCreate(irRecieveTask,"irRecieveTask", 1048, NULL, 1, NULL);
}

void loop() {
//  irObj.printIRData();
//  delay(1000);
}

void irRecieveTask(void *pvParameters) {
  while(1) {
    irObj.irRecieve();
  }
}

 回路については以下を参考に、PIN1にGPIO25、PIN2にGND、PIN3に3V3を繋ぎます。
f:id:rikoubou:20170612140529p:plain

 プログラムを書き込んでからシリアルモニタを立ち上げると「Init complete」の文字が現れます。
 その後にNEC方式の赤外線リモコンの信号を送ると、カスタムコードとコマンドが表示されます。

 たとえば以下のリモコンのAボタンを押した場合、シリアルモニタには次のように表示されます。
オプトサプライ赤外線リモコン: センサ一般 秋月電子通商 電子部品 ネット通販

f:id:rikoubou:20170627172611p:plain


3:送信側について
・ESP32_IRsend.h

/**
 * 参考URL:http://garretlab.web.fc2.com/arduino/lab/infrared_controller/
 */
#include <Arduino.h>

#define HEADER_DATA_ON_NUM    345
#define HEADER_DATA_OFF_NUM  4500
#define SEND_DATA_ON_NUM       20

class ESP32_IRsend {
  public:
    ESP32_IRsend(int pinNum, byte customCode1, byte customCode2);
    void sendCommand(byte data);

  private:
    int _ledPin;   // 赤外線LEDピン
    byte _custom1; // カスタムコード1
    byte _custom2; // カスタムコード2

    void sendIRData(byte data);
    void on(int num);
    void waitMicroSeconds(uint32_t waitTime);
};


・ESP32_IRsend.cpp

/**
 * 参考URL:http://garretlab.web.fc2.com/arduino/lab/infrared_controller/
 */
#include "ESP32_IRsend.h"

/**
 * コンストラクタ(LEDピンとカスタムコードを設定)
 * pinNum:赤外線LEDピン番号、customCode1:カスタムコード1、customCode2:カスタムコード2
 */
ESP32_IRsend::ESP32_IRsend(int pinNum, byte customCode1, byte customCode2) {
  _ledPin = pinNum;
  _custom1 = customCode1;
  _custom2 = customCode2;
  pinMode(_ledPin, OUTPUT);
}

/**
 * コマンドを送信する関数
 */
void ESP32_IRsend::sendCommand(byte data){
  on(HEADER_DATA_ON_NUM);                // ヘッダーコード(ON):ESP32の場合
  waitMicroSeconds(HEADER_DATA_OFF_NUM); // ヘッダー待機
   
  sendIRData(_custom1);                  // カスタムコード1を送信
  sendIRData(_custom2);                  // カスタムコード2を送信
  sendIRData(data);                      // コマンドを送信
  sendIRData(~data);                     // コマンドのbitを反転させたものを送信
   
  on(SEND_DATA_ON_NUM);                  // stop bit(ESP32の場合)
}

/**
 * 赤外線を送信する関数
 */
void ESP32_IRsend::sendIRData(byte data) {
  for(int i = 0; i < 8; i++) {
    on(SEND_DATA_ON_NUM);
    switch(data & 1) {
    case 0:
        waitMicroSeconds(565);
      break;
    case 1:
      waitMicroSeconds(1690);
      break;
    }
    data = data >> 1;
  }
}

/**
 * 赤外線LEDを点滅させる関数
 */
void ESP32_IRsend::on(int num) {
  for(int i = 0; i < num; i++) {
    GPIO.out_w1ts = (1 << _ledPin);
    waitMicroSeconds(9);
    GPIO.out_w1tc = (1 << _ledPin);
    waitMicroSeconds(17);
  }
}

/**
 * μs待機させる関数
 * ※ESP32にdelayMicrosecondsがなかったので、ループで代用
 */
void ESP32_IRsend::waitMicroSeconds(uint32_t waitTime) {
  uint32_t startTime = system_get_time();
  uint32_t nowTime = system_get_time();
  while ((nowTime - startTime) < waitTime) {
    nowTime = system_get_time();
  }
}


・ESP32_IRsendTest.ino

#include "ESP32_IRsend.h"

const int IR_LED = 25;
const byte CUSTOM1 = 0x10;
const byte CUSTOM2 = 0xef;

const int LED_PIN = 26;

ESP32_IRsend irSend(IR_LED, CUSTOM1, CUSTOM2);

void setup() {
  pinMode(LED_PIN, OUTPUT);
}

void loop() {
  // 3秒ごとに赤外線信号を送信
  digitalWrite(LED_PIN, HIGH);
  irSend.sendCommand(0xf8);
  digitalWrite(LED_PIN, LOW);
  delay(3000);
}

 回路はGPIO25を赤外線LEDに、GPIO26を普通のLEDに繋ぐだけです。それぞれのLEDに抵抗を入れるのを忘れないようにしましょう。

 プログラムを書き込むと普通のLEDが3秒間隔に短く光ります。この光っている間に赤外線LEDから赤外線信号が送信されています。


4:送信・受信確認
 受信側のESP32のシリアルモニタを立ち上げた状態で、送信側の赤外線LEDを受信側の受光器に近づけましょう。
 すると2:受信側についての例で上げた画像と同じカスタムコードとコマンドが表示されるはずです。

 受信結果がエラーばかりになる場合は、送信側の各種定数を調整すると受信成功率が上がります。


 以上がESP32での赤外線通信方法になります。ネットで探してもESP32での赤外線の情報がほとんどなく、ここまでやるのに2週間近くかかってしまいました。正直ごり押ししてる部分もあるので、もっと賢いやり方があれば教えていただけると幸いです。


・参考
esp-idf/infrared_nec_main.c at master · espressif/esp-idf · GitHub
赤外線リモコンの実験
赤外線リモコンの通信フォーマット

【ESP32】自作ゼルダエフェクトーンを改良してみた

 前にゼルダエフェクトーンを自作してみましたが、電池の持ちが悪すぎたので今回スリープモードを導入した改良版を作りました。
 具体的には「ドアを開けた時だけ起動して、ドアが閉まっている時にはスリープモードになる」ようにしています。

 回路も一部変更しているので注意してください。使う材料は全く同じです。


1:回路図改良版
f:id:rikoubou:20170622121049p:plain
 修正箇所は「マグネットスイッチから出ているIO12に繋いでいた線をGNDに繋げている」点のみです。IO14のピンのHIGH、LOWでスリープモードの切り替えを行うようにプログラムも修正します。


2:プログラム改良版
 改良したプログラムは以下のようになります。

・Effectone_v2.ino

#include <esp_deep_sleep.h>

#define BUZZER_PIN 23       // ブザーを鳴らすためのピン
#define JUDGE_PIN 14        // 磁石スイッチの判断をするピン

#define BEAT 150            // 一つの音を鳴らす時間
#define LEDC_CHANNEL 0      // チャンネル
#define LEDC_TIMER_BIT 13
#define LEDC_BASE_FREQ 5000

void setup() {
  // マグネットスイッチに関係するピンの準備
  pinMode(JUDGE_PIN, INPUT);

  // 音を鳴らす準備
  ledcSetup(LEDC_CHANNEL, LEDC_BASE_FREQ, LEDC_TIMER_BIT);
  ledcAttachPin(BUZZER_PIN, LEDC_CHANNEL);

  callZeldaSound();   // ゼルダの謎解き音を鳴らす

  // スリープのための諸々の設定(今回はGPIO14ピンを使う)
  esp_deep_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_AUTO);
  gpio_pullup_en(GPIO_NUM_14);
  gpio_pulldown_dis(GPIO_NUM_14);
  esp_deep_sleep_enable_ext0_wakeup(GPIO_NUM_14, 1); // GPIO14がHIGHになったらスリープモード解除

  // 扉が閉まるまで待つ
  do {
    ;
  } while (digitalRead(JUDGE_PIN) == HIGH);

  // 扉が閉まったらスリープモード実行
  esp_deep_sleep_start();
}

void loop() {
  // loopではなにもしない
}

/**
 * ゼルダの謎解き音
 */
void callZeldaSound() {
  ledcWriteTone(LEDC_CHANNEL, 3136); // ソ
  delay(BEAT);
  ledcWriteTone(LEDC_CHANNEL, 2960); // ♯ファ
  delay(BEAT);
  ledcWriteTone(LEDC_CHANNEL, 2489); // ♯レ
  delay(BEAT);
  ledcWriteTone(LEDC_CHANNEL, 1760); // ラ
  delay(BEAT);
  ledcWriteTone(LEDC_CHANNEL, 1661); // ♯ソ
  delay(BEAT);
  ledcWriteTone(LEDC_CHANNEL, 2637); // ミ
  delay(BEAT);
  ledcWriteTone(LEDC_CHANNEL, 3322); // ♯ソ
  delay(BEAT);
  ledcWriteTone(LEDC_CHANNEL, 4186); // ド
  delay(BEAT);
  ledcWriteTone(LEDC_CHANNEL, 0);    // 音を止める
}

 loop関数での処理を一切やめてsetup関数のみで色々やるようにしています。

 今回使用しているマグネットスイッチは磁石が近づいている時がLOW、磁石が遠くにある時がHIGHなので、無限ループで扉が閉まるまで待っています。扉がしまったらスリープモードに移行するようにしています。

 今回の改良で前回よりは電池の持ちがよくなりました。体感的には1日以上持つようになりました。(改良前は数時間で電池切れしていた)

 ESP32は結構電池を食うので、コンセントから電源を取らない場合はスリープモードも考慮する必要があるようです。


■改良前の記事とスリープモードについて
【ESP32】ゼルダエフェクトーンを自作してみた - ソースに絡まるエスカルゴ
【ESP32】スリープモードについて - ソースに絡まるエスカルゴ

【ESP32】マルチタスク中におけるdelayについて

rikoubou.hatenablog.com

 前回取り上げたマルチタスクの続きです。ESP32のタスクの中でミリ秒単位やマイクロ秒単位でのdelayを行う方法について色々わかったので記録しておきます。

 結論から言うと、実機で検証した結果マルチタスク中ではdelay、delayMicroseconds、microsといったArduinoでの時間を扱う関数が無効になるので代替関数を使用しなくてはならない」というものです。


1:ESP32で動いているOS
 前回のマルチタスクの記事の参考記事にあるように、ESP32で動いているOSは「freeRTOS」というOSらしいです。マルチタスクは、ArduinoではなくこのOSに対応する記述をすることで実現しています。
 つまり、マルチタスク中ではfreeRTOSに即した記述をする必要があるのです。
(※上記の理由から、Arduinoのdelayなどの関数がマルチタスク内で無効になっていると考えられます)


2:マルチタスク中におけるdelayの書き方
 では実際にdelayを行う方法です。以下のように「vTaskDelay」関数を使います。

void hoge() {
  Serial.println("hoge");
}

void hello() {
  const portTickType yDelay = 1000 / portTICK_RATE_MS; // 1000ms
  while(1) {
    Serial.println("hello");
    vTaskDelay(yDelay); // delay
  }
}

void hogeTask(void *pvParameters) {
  const portTickType xDelay = 3000 / portTICK_RATE_MS; // 3000ms
  while(1) {
    hoge();
    vTaskDelay(xDelay); // delay
  }
}

void helloTask(void *pvParameters) {
  hello();
}

void setup() {
  Serial.begin(115200);
  xTaskCreate(hogeTask,"hogeTask", 1024, NULL, 1, NULL);   // hogeTaskタスク登録して実行
  xTaskCreate(helloTask,"helloTask", 1024, NULL, 2, NULL); // helloTaskタスク登録して実行
}

void loop() {
}

 上記のプログラムを書き込んでシリアルモニタを立ち上げると、1秒ごとにhello、3秒ごとにhogeが出力されます。

const portTickType yDelay = 3000 / portTICK_RATE_MS; // 3000ms
const portTickType yDelay = 1000 / portTICK_RATE_MS; // 1000ms

 vTaskDelay引数の計算で直接の数値ではなくportTICK_RATE_MSを用いているのは、freeRTOSはどうやらTICKという単位で処理をしているらしく、portTICK_RATE_MSという「1ミリ秒辺りのTICK」の基準を使って正確な値を出すためです。
(※ちゃんと調べてないので合ってるかの確証はありません)


■2017/06/27 追記・修正
 修正前に「vTaskDelayでマイクロ秒単位でdelayができる」と書きましたが、実際に調べていくとそれは間違いで「vTaskDelayではマイクロ秒単位のdelayはできない」ことがわかりました。誤った記述をしてしまい、申し訳ありませんでした。

vTaskDelay((8 / portTICK_RATE_MS) / 1000); // 8μs delayのつもり

 上記のような記述をしてもコンパイルは通りますが、vTaskDelay中の処理では「引数の小数点以下は切り捨て」扱いとなるため、引数に0が代入されたのと同じ結果になります。
 よってvTaskDelayでマイクロ秒単位でのdelayはできません。


 以上がマルチタスク中でdelayを使う方法になります。マルチタスク中にdelayMicrosecondsなどがあってもコンパイルが通るため、実機でこの現象に気づくまでにえらく時間がかかりました。またそれをどうにか解決する方法にたどり着くまでも苦労しました。

 ネットで探してもイマイチ情報がないようなので、この記事が役に立てば幸いです。


■参考記事
なんか作ろうよ ESP-WROOM-32でマルチタスク
FreeRTOS のメモ - Qiita

【ESP32】マルチタスクについて

 ESP32でマルチタスクをする方法を探していたら見つけたので記録しておきます。

■ESP32でマルチタスクを行うプログラム
 基本的には参考記事のところにあるソースで良いのですが、なぜか自分のに書き込むと動かなかったので少し修正しました。

int maincount = 0; // メインカウンタ

// 裏で動かすタスク
void testTask(void *pvParameters) {
  int subcount = 1000; // サブカウンタ
  while(1) {
    Serial.println("sub task " + String(subcount));
    subcount++;
    delay(1000);
  }
}

void setup() {
 Serial.begin(115200);
 // タスクの登録(1とある引数が多分タスクのナンバリング)
 xTaskCreate(testTask,"testTask", 1024, NULL, 1, NULL);
}

void loop() {
  // loopの中では別の処理
  Serial.println("arduino loop "+ String(maincount));
  maincount++;
  delay(1000);
}

 上記をESP32に書き込んでからシリアルモニタを立ち上げると、以下のように二つのカウントアップ文字列が表示されます。
f:id:rikoubou:20170612153723p:plain


■参考記事
なんか作ろうよ ESP-WROOM-32でマルチタスク

■追加情報
マルチタスクでさらにわかったことを記事にしました。
rikoubou.hatenablog.com

【ESP32】赤外線通信について

 ESP32で赤外線を使って色々やってみようと思ったですが、ArduinoUnoで使えていたライブラリ等がESP32ではまだないようです。
 色々調べて行くとライブラリを使わずに赤外線通信を行なっている記事を見つけたので、それを元にESP32で動かした時のことを備忘録として記録しておきます。


・2017/06/27追記:より実用的な方法の記事を書きました
rikoubou.hatenablog.com



1:材料
 今回自分が使ったのは以下のものですが、すべての材料は安いものでも大丈夫だと思います。

・赤外線LED
3φ砲弾型赤外線LED 940nm LIR034の通販ならマルツオンライン

・赤外線受光器
リモコン受光モジュール RPM7138-Rの通販ならマルツオンライン

・適当な単色LED
・ESP32-DevKitC×2
・単3電池ボックス(必要に応じて)
・200Ω程度の抵抗×2
・ブレッドボード×2


2:受信側について
・プログラム

#define READ_PIN 26
#define LOW_STATE 0
#define HIGH_STATE 1
 
void setup(){
  Serial.begin(115200);
  pinMode(READ_PIN,INPUT);
 
  Serial.println("Ready to receive");
}
 
void waitLow() {
  while (digitalRead(READ_PIN)==LOW) {
    ;
  }
}
 
int waitHigh() {
  unsigned long start = micros();
  while (digitalRead(READ_PIN)==HIGH) {
    if (micros() - start > 5000000) {
      return 1;
    }
  }
  return 0;
}
 
unsigned long now = micros();
unsigned long lastStateChangedMicros = micros();
int state = HIGH_STATE;
 
void loop() {
    if (state == LOW_STATE) {
      waitLow();
    } else {
      int ret = waitHigh();
      if (ret == 1) {
        Serial.println("");
        return;
      }
    }
 
    now = micros();
    Serial.print((now - lastStateChangedMicros) / 10, DEC);
    Serial.print(",");
    lastStateChangedMicros = now;
    if (state == HIGH_STATE) {
      state = LOW_STATE;
    } else {
      state = HIGH_STATE;
    }
}

 基本的には参考記事にあるものそのままです。

 回路は、材料にあげたのと同じものを使うのであれば、以下の赤外線受光器のデータシートにあるように繋ぎます。
f:id:rikoubou:20170612140529p:plain

 シリアルモニタを立ち上げてESP32のリセットボタンを押すと、「Ready to receive」が表示されます。
 この状態で赤外線受光器にリモコンを向けてボタンを押すと数値の羅列が表示され、受信できていることが確認できます。


3:送信側について
・プログラム

const int IR_SEND_PIN = 33; // 赤外線LEDのピン
const int LED_PIN = 25;     // LEDのピン

// 送信データ
unsigned int data[] = {1000,2000,3000,4000,5000,6000,7000};

void setup() {
  pinMode(IR_SEND_PIN, OUTPUT);
  pinMode(LED_PIN, OUTPUT);
}

void loop() {
  digitalWrite(LED_PIN, HIGH);
  sendSignal(); // 赤外線信号を送信
  digitalWrite(LED_PIN, LOW);
  delay(3000);
}

// dataの赤外線信号を送信
void sendSignal() {
  int dataSize = sizeof(data) / sizeof(data[0]);
  for (int cnt = 0; cnt < dataSize; cnt++) {
    unsigned long len = data[cnt]*10;
    unsigned long us = micros();
    do {
      digitalWrite(IR_SEND_PIN, 1 - (cnt&1));
      delayMicroseconds(8);
      digitalWrite(IR_SEND_PIN, 0);
      delayMicroseconds(7);
    } while (long(us + len - micros()) > 0); // 送信時間に達するまでループ
  }
}

 こちらも参考記事にあるものほぼそのままです。

 回路図も書くほどではないので省きます。LEDの足の長い方と出力ピンの間に抵抗を入れ、足の短い方をGNDに繋ぐだけです。
 単色LEDは赤外線が送信されていることを肉眼で確認するためです。


4:送受信の確認
 受信側、送信側のどちらも準備ができたら、両方とも電源を入れて赤外線LEDが赤外線受光器に当たるようにします。
 すると以下のような値が受信側のシリアルモニタに表示されるはずです。(以下の例では2回送信しています)
f:id:rikoubou:20170612141632p:plain

 一番最初の大きな値は無視します。それ以降はdataに設定した値とほぼ同じ値が送信されていることがわかります。
 何度か試した感じでは、誤差は最大で±20程度のようです。またdataとして送る一つの信号の長さは長い方が(1000以上ぐらい)の方が良いようです。


 以上、ESP32を使っての赤外線通信についてでした。


■参考記事
Arduinoで赤外線リモコンの信号を解析する
Arduinoで赤外線リモコン信号を発信する

【ESP32】スリープモードについて

 前に作成した自作ゼルダエフェクトーンが電池の持ちがなさすぎたので、ESP32でのスリープモードについて調べました。


■スリープモードについて
 結論からとしては以下のプログラムで実装できます。

#include "esp_deep_sleep.h"

void setup() {
  Serial.begin(115200);
  Serial.println("start ESP32");
  Serial.println("config IO");
  
  // スリープのための諸々の設定(今回はGPIO0ピンを使う)
  esp_deep_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_AUTO);
  gpio_pullup_en(GPIO_NUM_0);    // use pullup on GPIO
  gpio_pulldown_dis(GPIO_NUM_0); // not use pulldown on GPIO

  esp_deep_sleep_enable_ext0_wakeup(GPIO_NUM_0, 0); // 指定したGPIOがLowの時に起動

  delay(5000);
  Serial.println("deep sleep start!");
  esp_deep_sleep_start(); // スリープモード実行
}

void loop() {
  
}

 このプログラムをESP32に書き込んでからシリアルモニタを立ち上げ、「deep sleep start!」が表示されたあとにスリープモードに入ります。

 今回はGPIO0ピンをLowにすると起動するように設定しているので、GPIO0をGNDに落とすと再び「start ESP32」や「deep sleep start!」が表示されます。またesp_deep_sleep_enable_ext0_wakeup関数の第二引数を1にすると、GPIO0がHighの時に起動するというようにできます。

 色々な関数で使われている「GPIO_NUM_0」という引数の意味は、ESP32のライブラリで使用されている定数で、GPIO_NUM_0〜GPIO_NUM_40まであります。gpio_num_tの型エラーが出た場合はこの定数を使用します。

 今回はGPIOピンからの起動を行いましたが、時間による起動もできるようです。(参考記事より)

 実際に測定はしてないのでわかりませんが、スリープモードだとかなりの節約ができるみたいです。常時駆動させるような場合はスリープモードが必須になると思うので、ご参考になれば。

(一日に二回も更新するなんて偉いぞ、自分)


・参考記事
ESP32 Deep Sleep のテスト (Hibernation mode) - ブログ/こばさんの wakwak 山歩き
pcbreflux: ESP32 Deep Sleep Example