工作と競馬

電子工作、プログラミング、木工といった工作の記録記事、競馬に関する考察記事を掲載するブログ

概要

ESP-WROOM-32のタイマー割り込みを使って一定周期でAD変換するプログラムを作成し、動作を確認した。


背景と目的

ESP-WROOM-32で一定周期でAD変換をする必要が出たので、サンプルプログラムをもとに、作成してみる。


詳細

1.サンプルプログラムを調べる

ESP-IDFのexamplesに、

のサンプルがあるので、これらをうまく応用すれば作れそう。ちなみに、タイマー割り込みは、こちらにAPIリファレンスがあるが、これを見ても全然イメージがわかないので、サンプルを見たほうが早い。


2.コーディング

ここでは、peripherals/timer_group/main/timer_group.cを基本骨格として、不要部分の削除とAD変換処理の追加をしていく。

2.1 タイマー割り込み

peripherals/timer_group/main/timer_group.cではタイマーグループ0の2つのタイマーを使っているがここでは、タイマー0だけを使う。なので、timer_group0_isr関数のタイマー1の割り込みに関する処理であるelse if節を削除。(削除するだけなので、コードは省略)

example_tg0_timer1_initは使用しないのでコメントアウトし、app_mainでの呼び出し部もコメントアウト。

// example_tg0_timer1_init();

また、割り込み周期は、冒頭のマクロ定義で、設定。今回は100msごととしたいので、以下。

#define TIMER_INTERVAL0_SEC   (0.1)   /*!< test interval for timer 0 */
2.2 AD変換処理

peripherals/adc/main/adc1_test.cを参考にする。まず、必要なヘッダをインクルード。

#include "driver/gpio.h"
#include "driver/adc.h"

初期化処理としてチャンネルやビット深度等を設定。

#define ADC1_TEST_CHANNEL (4) // GPIO32=ADC1のチャンネル4を使用
adc1_config_width(ADC_WIDTH_12Bit);
adc1_config_channel_atten(ADC1_TEST_CHANNEL,ADC_ATTEN_11db);

AD変換は、以下の感じでできる。

int v = adc1_get_voltage(ADC1_TEST_CHANNEL);

タイマー割り込みでAD変換を実行するには、timer_example_evt_task関数のwhileループ内で上記を行うえばよい。

static void timer_example_evt_task(void *arg)
{
    while(1) {
        timer_event_t evt;
        // timer_group0_isrでキューにデータが送られたら受信
        xQueueReceive(timer_queue, &evt, portMAX_DELAY);
        // AD変換して表示
        double time;
        timer_get_counter_time_sec(evt.group, evt.idx, &time);
        printf("time: %.3f S, ADC value: %d\n", time, adc1_get_voltage(ADC1_TEST_CHANNEL));
    }
}

timer_group0_isrが呼び出されると、

xQueueSendFromISR(timer_queue, &evt, NULL);

でキューに送信され、timer_example_evt_task側のxQueueReceiveでそれを受け取るので、結果的に割り込み発生ごとにAD変換が実行される。

最後に、app_mainをまとめて書いておくと、以下。

void app_main()
{
    // タイマー初期化
    timer_queue = xQueueCreate(10, sizeof(timer_event_t));
    example_tg0_timer0_init();

    // ADC初期化
    adc1_config_width(ADC_WIDTH_12Bit);
    adc1_config_channel_atten(ADC1_TEST_CHANNEL,ADC_ATTEN_11db);

    // タスク登録
    xTaskCreate(timer_example_evt_task, "timer_evt_task", 2048, NULL, 5, NULL);
}

3.動作確認

上記を書き込み実行したときのシリアルコンソールは以下。変換時刻と一緒に表示しているが、ちょうど0.1秒ごとに実行され、正しく動作している。

20170820001758


まとめ

ESP-WROOM-32のタイマー割り込みを使って一定周期でAD変換するプログラムを作成し、動作することを確認した。ほかの処理と並行して一定周期でセンシング等ができそうだ。

概要

ESP-WROOM-32でiBeaconをスキャンすることができた。


背景と目的

ESP-WROOM-32でiBeaconをスキャンする必要が出たため、スキャンするコードを実装してみる。


詳細

1.やること

とりあえず、スキャンできればいいので、最低限UUID、Major、Minorを読み出しシリアルで結果表示できれば良い。


2.コーディング

2.1 ESP-IDFのサンプルコード

ESP-IDFのサンプルで、bluetooth\gatt_clientというのがある。これは、BLEデバイスをスキャンしたり接続したりするサンプルだ。これを改造すればよさそう。


2.2 改造箇所

サンプルのesp_gap_cbという関数は、gap eventというものが起きたときに呼ばれるコールバック関数。(ble_client_appRegisterで、esp_ble_gap_register_callback(esp_gap_cb)という形でコールバックとして登録されている。)この関数の中のswitch文の分岐のうち、ESP_GAP_BLE_SCAN_RESULT_EVTという部分(サンプルでいうと、304行目)が、スキャン結果を受け取ったときに実行される。なので、この部分においてiBeacon検出に必要な処理をコーディングしてやればよさそうだ。


2.3 iBeaconを検出する処理

iBeaconを検出するには、ESP-IDFのAPIリファレンスによれば、アドバタイズされたデータが格納されているscan_result->scan_rst.ble_advというバイト配列を調べればよさそう。iBeaconの場合、こちらこちらこちらあたりを参考にすれば、26バイトのデータの内訳がわかるので、UUID、Major、Minor等を読み出せばよい。

というわけで、以下、読み出すための関数を作成。is_ibeaconは、ibeaconかどうかをアドバタイジングデータの長さとデータの先頭9バイトから判定する。

// iBeaconか調べる
bool is_ibeacon(uint8_t *ble_adv, uint8_t adv_data_len)
{
    bool tf;
    if (adv_data_len < 30) {
        return false; // 30バイト以下のときは不可
    }
    if (ble_adv[3] == 0x1a && ble_adv[4] == 0xff &&
        ble_adv[5] == 0x4c && ble_adv[6] == 0x00 &&
        ble_adv[7] == 0x02 && ble_adv[8] == 0x15) {
        // 4~9バイト目までが特定のデータになっている
        tf = true;
    } else {
        tf = false;
    }
    return tf;
}

// uuid
void get_ibeacon_uuid(uint8_t *ble_adv, char uuid[])
{
    sprintf(uuid, "%x%x%x%x-%x%x-%x%x-%x%x-%x%x%x%x%x%x",
     ble_adv[9], ble_adv[10], ble_adv[11], ble_adv[12], 
     ble_adv[13], ble_adv[14], 
     ble_adv[15], ble_adv[16], 
     ble_adv[17], ble_adv[18],
     ble_adv[19], ble_adv[20], ble_adv[21], ble_adv[22], ble_adv[23], ble_adv[24]);
}

// major
int get_ibeacon_major(uint8_t *ble_adv)
{
    return ble_adv[25] * 256 + ble_adv[26];
}

// minor
int get_ibeacon_minor(uint8_t *ble_adv)
{
    return ble_adv[27] * 256 + ble_adv[28];
}

次に、上記の関数を使って、ibeacon検出結果を表示する部分は以下。

case ESP_GAP_BLE_SCAN_RESULT_EVT: {
    esp_ble_gap_cb_param_t *scan_result = (esp_ble_gap_cb_param_t *)param;
    switch (scan_result->scan_rst.search_evt) {
    case ESP_GAP_SEARCH_INQ_RES_EVT:
        if (is_ibeacon(scan_result->scan_rst.ble_adv, scan_result->scan_rst.adv_data_len)) {
            // データを取得
            char uuid[8 + 1 + 4 + 1 + 4 + 1 + 4 + 1 + 12 + 1];
            get_ibeacon_uuid(scan_result->scan_rst.ble_adv, uuid); // uuid
            int major = get_ibeacon_major(scan_result->scan_rst.ble_adv); // Major
            int minor = get_ibeacon_minor(scan_result->scan_rst.ble_adv); // Minor
            int rssi = scan_result->scan_rst.rssi; // rssi
            // 表示
            ESP_LOGI(GATTC_TAG, "iBeacon detected!");
            ESP_LOGI(GATTC_TAG, "UUID = %s", uuid);
            ESP_LOGI(GATTC_TAG, "Major = %d, Minor = %d, RSSI = %d", major, minor, rssi);
        }
        break;

という感じ。


3.動作確認

上記をESP32に書き込み動作させたところ、手持ちのiBeaconを正しく検出できた。

20170817230606


まとめ

ESP-WROOM-32を使って、iBeaconの検出ができた。次は、SDカードに結果を保存してみようかと思う。

概要

ESP-WROOM-32にSDカードを接続し、ファイル書き込みをすることができた。


背景と目的

ESP-WROOM-32で、センサ計測値等をWi-Fi等で送信できない場合に、いったん保存しておきたい。そこで、SDカードを使ってファイルにデータを保存する方法を調べ、メモしておく。


詳細

1.材料

microSDカードは、WindowsPCのアプリケーションSDFormatterにて、FAT32でフォーマットしておいた。


2.接続

カードスロットとESP-WROOM-32の間の接続は、こちらこちらを参考にして、以下のような配線とした。写真は、ブレッドボードに組んだ様子。

20170803233952

図2.1 回路図

DSC_0082

図2.2 ブレッドボードに組んだ様子


3.ソフトウェア

ここでは、もっとも単純に、ArduinoのSDライブラリを使用して、"This is test."という文字列を10回書き込むだけとした。書き込み先は、ルートディレクトリの"test1.txt"というファイルとした。

#include <SD.h>

const uint8_t cs_SD = 5; // GPIO5=CS
const char* fname = "/test1.txt";

File fo; // ファイルオブジェクト
int i = 0;

void setup() {
  Serial.begin(115200);
  
  // SDライブラリを初期化
  SD.begin(cs_SD, SPI, 24000000, "/sd");

  // 書き込みモードでファイルを開く
  fo = SD.open(fname, FILE_WRITE);

  for (i = 0; i < 10; i++) {
    // 書き込む
    fo.println("This is test.");
    
    // 1秒待つ
    delay(1000);
    Serial.println(i, DEC);    
  }
  
  // ファイルを閉じる
  fo.close();
  
}

void loop() {

  Serial.println("Finish.");

}


4.動作確認

書き込んだSDカードをPCで開いたところ、ルートディレクトリに"test1.txt"というファイルができていた。中身を開くと、以下の通り10回分"This is test."が書き込まれていた。正しく、書き込みができたようだ。

20170803232159


まとめ

ESP-WROOM-32にSDカードを接続し、ファイル書き込みをすることができた。センサ等を接続して、計測値を記録するのに役立てられそうだ。

概要

NFCタグをタッチして、自作アプリの特定アクティビティを表示させる方法を調べ、まとめた。


背景と目的

NFCタグをタッチすることで、特定のアクティビティを選択することができれば、使用状況に応じて、簡単に必要な画面を表示できる。そこで、特定のアクティビティを指定する方法を調べて、まとめる。


詳細

0.方法を簡単に箇条書き

いろいろ探し回った結果、以下の手順を行えばよいということがわかった。

  • NFCタグに、アクティビティ選択のためのNDEFレコードを書き込む
  • Manifestファイルへのインテントフィルタの記述
  • デフォルトのアクティビティで、インテントとして渡されたNDEFレコードを解析しアクティビティを選択できるコードを実装


1.NFCタグ

アプリを起動するためのNFCタグは、通常、アプリケーションレコードというタイプのNDEFレコードを使う。しかし、これだけではアクティビティ選択の情報が何もないので、何らかのNDEFレコードにアクティビティ選択のための情報を追加する。自作アプリなので何でもいいと思うが、とりあえず一番簡単なテキストレコードを使う。ペイロードには、アクティビティ名を入れておく。(アクティビティが区別できればなんでもよい。)


2.Manifestファイル

次に、こちらによれば、Manifestファイルに、タグ読み取り時のインテントを受け取るためのインテントフィルタを設定する必要がある。

            <intent-filter>
                <action android:name="android.nfc.action.NDEF_DISCOVERED" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="text/plain" />
            </intent-filter>


3.コーディング

2の記述により、デフォルトのアクティビティ渡されたインテントには、NFCタグのNDEFレコードが格納されているので、1で設定したテキストレコードを解析し、目的のアクティビティを選択するコードを書く。OnCreateメソッドに実装すれば、デフォルトのアクティビティ起動後、すぐに目的のアクティビティが表示できる。

        // アクティビティに渡されたインテントを取得
        Intent intent = this.getIntent();
        // NDEFを取得し解析する
        Parcelable[] parcelables = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
        if (parcelables != null) {
            for (Parcelable parcelable : parcelables) {
                NdefMessage msg = (NdefMessage) parcelable;
                NdefRecord[] records = msg.getRecords();
                for (NdefRecord record : records) {
                    // ここで、テキストレコードを解析
                    // 条件に合致すれば、以下のように目的のアクティビティを
                    // 開始する
                    // レコードの解析方法はWeb各所等を参照
                    // Intent newIntent = new Intent(this, TargetActivity.class);
                    // startActivity(newIntent);
                }
            }
        }


4.動作確認

上記の内容を自作アプリに適用し、タグをかざしたところ、無事目的のアクティビティが表示された。というわけで、成功!


まとめ

NFCタグをタッチして、自作アプリの特定アクティビティを表示させる方法を調べ、実装、動作確認することができた。

PythonのIDE Sypderで、ブレイクポイントを張ってデバッグをしようと思ったら、いきなり以下のエラー。

  • Python 2.7.10
  • Spyder 2.3.5.0
  • Window7 32bit
Traceback (most recent call last):
  File "", line 1, in 
  File "C:\Python27\lib\site-packages\spyderlib\widgets\externalshell\sitecustomize.py", line 704, in debugfile
    debugger.run("runfile(%r, args=%r, wdir=%r)" % (filename, args, wdir))
  File "C:\Python27\lib\bdb.py", line 400, in run
    exec cmd in globals, locals
  File "", line 1, in 
  File "C:\Python27\lib\site-packages\spyderlib\widgets\externalshell\sitecustomize.py", line 684, in runfile
    execfile(filename, namespace)
  File "C:\Python27\lib\site-packages\spyderlib\widgets\externalshell\sitecustomize.py", line 63, in execfile
    scripttext = builtins.open(fname).read()+ '\n'
IOError: [Errno 2] No such file or directory: 'スクリプトのフルパス'

原因は、 C:\Python27\lib\site-packages\spyderlib\widgets\externalshell\sitecustomize.pyの509~510行目

# Breakpoints don't work for files with non-ascii chars in Python 2
# Fixes Issue 1484

を見る限り、non-ascii chars(要するに日本語)がデバッグ対象の.pyファイルのパスに含まれているせいのようだ。510行目に、この不具合を直したのか直す意志があるだけなのかよくわからない記述があるが、実際直っていない。日本語の含まれないパスに置いて再度実行したところ、ちゃんと動いた。

というわけで、Spyderでデバッグするときは、日本語が含まれないパスに置くこと。

このページのトップヘ