「IoT」タグアーカイブ

ESP32-IDFでUART通信を行ってみる

ESP32-IDFでUART機能を試してみます。

こんにちは、北神です。IoTではGPSや既存のシリアル通信機器が話題になることもあります。

さっそく、ESP32-IDFでUARTを試してみまいと思います。

詳しい資料は、以下の公式情報から辿ってみてください。

https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/uart.html

    #define UART_2_TXD  16
    #define UART_2_RXD  17

    const int uart_num = UART_NUM_2;
        uart_config_t uart_config = {
        .baud_rate = 9600,//115200,
        .data_bits = UART_DATA_8_BITS,
        .parity = UART_PARITY_DISABLE,
        .stop_bits = UART_STOP_BITS_1,
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
        .rx_flow_ctrl_thresh = 122,
    };
    uart_param_config(uart_num, &uart_config);
    uart_set_pin(uart_num, UART_2_RXD, UART_2_TXD, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
    uart_driver_install(uart_num, 128+1, 0,0, NULL, 0);

以上の通り、GPIO16とGPIO17(UART_2)をUART用に設定しました。
uart_configでは、見慣れたUARTの設定を細かく行っています。

uart_driver_installでハマった点は、rx_buffer_sizeが128以下に設定するとエラーがでるという点です。
多分、rx_buffer_sizeを用いてmallocを行なっている用に思えますので、小さすぎるバッファは作成できないのでしょう。
とりあえず、最小の128+1で構成すればOKです。

受信に関しては

    int len = uart_read_bytes(uart_num, data, 1 , 1000 / portTICK_RATE_MS);

で済みます。

uart_read_bytesは、公式より以下の関数になります。

    int uart_read_bytes(uart_port_tuart_num, void *buf, uint32_t length, TickType_t ticks_to_wait)

ここで、lengthを指定してあげれば、length分溜まったデータを取得することができます。
今回は、わざわざ1バイト毎の取得を行い改行文字でカッティングを行いたい為、lengthを1としました。

こんな感じで、UART通信も設定できるようです。

めでたし、めでたし。

それでは!

ESP32-IDFでI2C-Masterを試してみる

ESP32-IDF開発環境にてI2Cを操作する方法をまとめていきます。

こんにちは、北神です。IoTで良く使用するI2Cを使う場面もあるだろうと思い、まとめました。

詳しい資料は、以下の公式情報から辿ってみてください。

https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/i2c.html

また、以下の公式サンプルの一部を使用しております。

https://github.com/espressif/esp-idf/tree/master/examples/peripherals/i2c/i2c_tools

■ マスターのI2Cの設定

I2Cの設定情報を、以下の様に記載します。変更が多い SDAピン、SCLピン、クロック速度を外部で変更できるようにしてあります。
その他は固定で良いと思いますが、必要があれば変更してください。

#define I2C_MASTER_TX_BUF_DISABLE 0
#define I2C_MASTER_RX_BUF_DISABLE 0 
#define WRITE_BIT I2C_MASTER_WRITE 
#define READ_BIT I2C_MASTER_READ    
#define ACK_CHECK_EN 0x1           
#define ACK_CHECK_DIS 0x0         
#define ACK_VAL 0x0                 
#define NACK_VAL 0x1                

#define I2C_SDA     21
#define I2C_SCL     22

void i2c_master_setup(int sda_io_num,int scl_io_num,int clk_speed,i2c_port_t i2c_port)
{
    int i2c_master_port = 0;
    i2c_config_t conf = {
        .mode = I2C_MODE_MASTER,
        .sda_io_num = sda_io_num,
        .sda_pullup_en = GPIO_PULLUP_ENABLE,
        .scl_io_num = scl_io_num,
        .scl_pullup_en = GPIO_PULLUP_ENABLE,
        .master.clk_speed = clk_speed,
        
    };
    i2c_driver_install(i2c_port, I2C_MODE_MASTER, I2C_MASTER_RX_BUF_DISABLE, I2C_MASTER_TX_BUF_DISABLE, 0);
    i2c_param_config(i2c_port, &conf);
}

■ i2cdetectを試しに作ってみる

I2Cの設定を記述できたので、早速試してみようと思いました。
一番簡単で便利なi2cdetectをカバーしてみます。
多くの処理は、公式のサンプルを利用しています。

void app_main(void)
{

    i2c_port_t i2c_port = I2C_NUM_0;
    i2c_master_setup(I2C_SDA,I2C_SCL,100000,i2c_port);


    uint8_t address;
    printf("     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f\r\n");
    for (int i = 0; i < 128; i += 16) {
        printf("%02x: ", i);
        for (int j = 0; j < 16; j++) {
            fflush(stdout);
            address = i + j;
            i2c_cmd_handle_t cmd = i2c_cmd_link_create();
            i2c_master_start(cmd);
            i2c_master_write_byte(cmd, (address << 1) | WRITE_BIT, ACK_CHECK_EN);
            i2c_master_stop(cmd);
            esp_err_t ret = i2c_master_cmd_begin(i2c_port, cmd, 50 / portTICK_RATE_MS);
            i2c_cmd_link_delete(cmd);
            if (ret == ESP_OK) {
                printf("%02x ", address);
            } else if (ret == ESP_ERR_TIMEOUT) {
                printf("UU ");
            } else {
                printf("-- ");
            }
        }
        printf("\r\n");
    }

    i2c_driver_delete(i2c_port);
    return 0;
}

プログラムを書込み、実行すると、以下の様にコンソールに表示されました。

めでたし、めでたし。

それでは!

ESP32-IDFで自作ライブラリを反映させる方法

ESP32-IDFで自作のライブラリや、他所のライブラリを使用したい時の方法です。

こんにちは北神です。

あまり多くはありませんが、自作したライブラリを反映させる際に、困ったのでまとめます。

作成しらプロジェクフォルダの中に、mainフォルダがあり、

ここにapp_main()が記載されたファイルが生成されると思います。

安易な方法ですが、そのmainフォルダに、反映させたいヘッダーファイルを入れます。

このフォルダ内に「CMakeLists.txt」があり、それを編集します。

idf_component_register(SRCS "app_mainのファイル.c" "リンクさせたいライブラリ.c"
INCLUDE_DIRS ".")

これで、反映させたいヘッダーファイルもコンパイルされて、バイナリファイルが生成されます。

一安心。

※カッコ内はカンマも要らないみたいです。

ではでは。

地図のオープンデータ「OpenStreetMap」

地図を使ったサービスを考える場合はGoogle Mapを使用しますが、
「$200 usage every month for no charge. That’s 28,500 maploads per month for no charge. 」
(https://mapsplatform.google.com/pricing/?hl=ja)
とあり、有名サイトになった場合には、有料になります。

そこで地図のオープンデータの使用を考えてみよう・・・・
OpenStreetMap(OSM)は、誰でも自由に地図を使えるよう、みんなでオープンデータの地理情報を作るプロジェクトです。
https://openstreetmap.jp
「OpenStreetMapとその協力者をクレジットすれば、データを自由にコピー、配布、送信、利用することができます。変更したり翻案したりしたデータは同じライセンスに従う場合のみ、提供することができます。」

Googleマップのようにマーカーを付けることが可能。
https://www.openstreetmap.org/?mlat=緯度&mlon=経度

弊社の位置だと、こちら。
https://www.openstreetmap.org/?mlat=35.70010&mlon=139.77402

ZOOMしたい場合は、URLに#map=ズームの数/緯度/経度を追加。
https://www.openstreetmap.org/?mlat=35.70010&mlon=139.77402#map=17/35.70010/139.77402

埋め込みも可能。

大きな地図を表示

公開されているGPSトラッキングが見えるので、精度の悪いGPS端末があるとズレることはあるが参考値としてはよいと思う。

弊社秋葉原駅付近のトラッキングデータ。

“Base map and data from OpenStreetMap and OpenStreetMap Foundation”

ESP32-IDFでアプセスポイントを構築する

ESP32-IDFでAPモードで起動する方法を試してみます。

こんにちは北神です。

ESP32はArduino環境とチップメーカ公式のIDFという開発環境があります。
日本ではArduino環境での開発が多くみられますが、ESP32の本場はIDFによる構築ですので、それに合わせて開発を試してみたいと思います。

今回試すのは、アクセスポイント(APモード)にする方法です。

細かく説明するには、FreeRTOSについてや、イベントについて話す必要がありますが、今回はサクッと動作するソースを載せておきます。

開発環境は ESP-IDF v4.3.1 を使用しております。

#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"

#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include <lwip/netdb.h>


void wifi_init_softap(void);

static const char *TAG = "ESP32_WS_APP";

//==========================//
// wifi_event_handler
//==========================// 
static void wifi_event_handler(void* arg, esp_event_base_t event_base,
                                    int32_t event_id, void* event_data)
{
    if (event_id == WIFI_EVENT_AP_STACONNECTED) {
        wifi_event_ap_staconnected_t* event = (wifi_event_ap_staconnected_t*) event_data;
        ESP_LOGI(TAG, "station "MACSTR" join, AID=%d",
                 MAC2STR(event->mac), event->aid);
    } else if (event_id == WIFI_EVENT_AP_STADISCONNECTED) {
        wifi_event_ap_stadisconnected_t* event = (wifi_event_ap_stadisconnected_t*) event_data;
        ESP_LOGI(TAG, "station "MACSTR" leave, AID=%d",
                 MAC2STR(event->mac), event->aid);
    }
}

#define SSID    "ESP32_TEST"
#define PASS     "123456789012345"
//==========================// 
//  WiFi SoftAP
//==========================// 
void wifi_init_softap(void)
{
    esp_netif_create_default_wifi_ap();
 
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));
 
    ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
                                                        ESP_EVENT_ANY_ID,
                                                        &wifi_event_handler,
                                                        NULL,
                                                        NULL));
    wifi_config_t wifi_config = {
        .ap = {
            .ssid = SSID,
            .ssid_len = strlen(SSID),
            .channel = 7,
            .password = PASS,
            .max_connection = 2,
            .authmode = WIFI_AUTH_WPA_WPA2_PSK
        },
    };
    if (strlen(PASS) == 0) {
        wifi_config.ap.authmode = WIFI_AUTH_OPEN;
    }
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
    ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_AP, &wifi_config));
    ESP_ERROR_CHECK(esp_wifi_start());
}

//==========================// 
//  app_main
//==========================// 
void app_main(void)
{
    ESP_ERROR_CHECK(nvs_flash_init());
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());

    ESP_LOGI(TAG, "ESP_WIFI_MODE_AP");    
    wifi_init_softap();
}

このソースを
idf.py flash -p COM11 で書き込み、 idf.py monitor -p COM11 で確認すると、以下の様にログが表示されます。


この状態で、スマフォ等でWiFiのSSIDを確認しに行くと、
SSID: ESP32_TEST
Pass: 123456789012345
で、接続しに行く事ができます。

無事に接続ができますと、IPを振り分けられます。
ログでは以下の様に表示されます。

これで、クライアントに「192.168.4.2」を配布できました。

ESP32-IDFはFreeRTOSで開発する為、一般的なシングルスレッドなマイコンの動作ではなく、決まった時間でタスクが切り替わる事を意識しながら開発する必要があります。

FreeRTOSは非常に便利な機能が多く、できなかった事が楽にできる様になりますのでおすすめです。

ではでは。

PythonでIoT用ミニマムな簡易APIサーバーを作る

Pythonでミニマム(最小形態)でサーバーを作ってみようと思う。

こんにちは、北神です。

IoTの開発を行う際に、ちょっとテストでサーバーが欲しい!という時に、Linuxを立ち上げアレやコレを入れて、
HTTPのPOSTやGETを取得するサーバーを組むのは時間がかかって仕方がない。

そこで、Pythonでお手軽にAPIサーバーの作り方をまとめていきます。

■ bottleを始めてみる

使うライブラリは「bottle」です。

このbottleは、高性能ではありませんが、数行でサーバーを立ち上げる事ができ、
POSTやGETといったREST-fullの構造を構築する事ができます。
また、HTMLの表示等もできますので、簡易な試験サーバーとして使用する際に重宝します。

早速インストールします

pip3 install bottle

これだけです。
また、bottleは1ソースで構成されたライブラリであることも人気の秘訣であり、以下のULRよりダウンロードすることで使用することも可能です。
https://bottlepy.org/bottle.py

早速、簡単なので以下のソースをテキストに貼り、server.pyと保存してみましょう。

from bottle import Bottle, run , post, get

app = Bottle()

@app.route('/')
def hello():
    return "Hello World!"

run(app, host='localhost', port=8080)

実行してみます。

python3 server.py

すると、コマンドラインには

Bottle v0.12.19 server starting up (using WSGIRefServer())...
Listening on http://localhost:8080/
Hit Ctrl-C to quit.

と表示されますので、試しにブラウザにて

localhost:8080 と入力して表示します。

Hello World! と表示されます。

■ bottleでAPI風を作成

早速API風な機能をつけていきます。

GETの機能は

@app.get('/get')
    def get_acction():
    return "GET Hello!"

で作成でき、

POSTの機能は

@app.post('/post')
    def post_acction():
    return "Post Hello!"

で作成できます。

これで、IoT機器でREST-fullの対応が試せる様になります。

ではでは。

ESP32-IDFをWindowsで構築する

ESP32では、公式開発環境のESP32-IDFとAeduinoIDEの二種類の開発環境があります。
今までArduinoで開発してましたが、今回からESP-32-IDFを試してみたいと思います。

■ インストールの方法

 https://dl.espressif.com/dl/esp-idf/?idf=4.4

Online Installerで環境全体のインストールが可能です。

インストール後、デスクトップに「ESP-IDF 4.3 CMD」が登場し、ESP-IDF 4.3 CMDを使用してコマンドを入力します。

■ プロジェクト作成

「ESP-IDF 4.3 CMD」にて、プロジェクトファイルを生成します。
プロジェクトファイルを生成したいディレクトリまでcdコマンドで移動し

idf.py create-project プロジェクト名

例)

idf.py create-project Hello

で、作成ができます。

■ プログラムを編集

プロジェクト名のフォルダにmainフォルダがあり、その中にC言語ソースがあります。
初期では


#include <stadio.h>
void app_main(void)
{

}

が書かれていると思います。

app_mainがmainでない点は、別途Bootloder等があり、app_mainが呼び出されるまで別の処理が先に行われます。

#include <stadio.h>
void app_main(void)
{
    printf("Hello\n");
}

保存し、以下のコマンドを実行します。

idf.py build

最初は時間かかりますが、2回め以降変更がない際は早くコンパイルができます。

※コンパイル中に「FAILED: esp-idf/mbedtls/x509_crt_bundle」というエラーが表示された際

このエラーは、cryptographyがバージョンアップした際に関数の使い方を変更した事が原因です。
問題のバージョンは、cryptographyの35.0.0 – 2021-09-29のものです。
IDF側で対応されてば良いですが、まだ対応されてない際は、以下のコマンドで一つ前のバージョンを使用します。

pip uninstall -y cryptography
pip install cryptography==3.4.8

■ ESP32に書き込む

buildがうまくいったら、以下のコマンドで書き込みます。

idf.py flash -p ポート番号

Windowsの場合、COMポートの指定は以下の例の様にします。

idf.py flash -p COM11

■ ESP32でシリアルモニタを確認する

以下のコマンドでシリアル通信の確認ができます。
デフォルトでは115200bpsになります。

idf.py -p com11 monitor

コマンドを使って、ESP32の開発ができそうです。

ではでは。

ESP32(Arduino)でタイマー割り込みを使う方法

ESP32(Arduino)でタイマー割り込みが使いたい!定時実行やセンサの状態取得等、一定時間が経過した際に実行させたい場合にタイマー割り込みを使いますが、
そんなタイマー割り込みをArduinoで使う方法をまとめました。

こんにちは、北神です。
ESP32(Arduino)でタイマーが使いたかったので試してみました。
以下のソースを貼り付けるだけで、タイマー割り込みが使用できます。

//===========================================================//
// Timer Interrupt
//===========================================================//
hw_timer_t *timer1 = NULL;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;

void IRAM_ATTR onTimer1()
{
portENTER_CRITICAL_ISR(&timerMux);
// 実行させたい関数をここに記載
example();
portEXIT_CRITICAL_ISR(&timerMux);
}

void Timer1_interrupt_setup(int usec)
{
timer1 = timerBegin(0, getApbFrequency()/1000000, true);
timerAttachInterrupt(timer1, &onTimer1, true);
timerAlarmWrite(timer1, usec, true);
timerAlarmEnable(timer1);
}

実行する際は、
Timer1_interrupt_setup(マイクロ秒) を記述して、その間隔で関数が実行されます。

ESP32では、HTTPの送信や受信で数秒程度、処理に時間がかかりますので、
その間にセンサーやスイッチの立ち上がりだけ見たい! ハードウェア特有の処理を済ませておきたい!という時に割り込みが使用できます。

ただ、何重にも割り込みは発生させることができませんし、割り込み後の処理で時間がかかる処理はご法度ですので、割り込みプログラムには留意が必要です。

ではでは。

RaspberryPiでプログラムを自動起動する方法

RaspberryPiでせっかく作ったプログラム、毎回コマンドやマウスで動かしてたらキリがない。
まして、電源入れたらひとりでに起動する組み込みで使用したい。自動化どうするんだっけ・・・

こんにちは、北神です。

RaspberryPiで、プログラムを自動起動する方法です。

古きLinuxユーザーの方は、init.dを想像するかもしれませんが、
今ではsystemdという仕組みが物凄く簡単ですので、ぜひsystemdを活用してみてください。

ます、systemdで起動する内容を設定する .serviceというファイルを作成します。
主にテキストのファイルですので、nanoやviで書き込みます。

[Unit]
Description= Custom Service
[Service]
Type=simple
ExecStart=/usr/bin/python3 /home/pi/service.py
[Install]
WantedBy=multi-user.target

Sytemdでは、service記述によりたくさんのオプションがあり、細かく設定する事ができます。
上記では、/home/piに保存したservice.pyをPython3で起動する単純な物です。

このファイルを、以下のコマンドでディレクトリにコピーします。
仮に hello.service としましょう。

sudo cp hello.service /etc/systemd/system/
sudo systemctl daemon-reload

これで、Systemdが読みに行けるファイルになりました。

実際に、起動を開始するには

sudo systemctl start hello.service 

終了する際は

sudo systemctl stop hello.service 

で行うだけです。

また、状態の確認は

sudo systemctl status hello.service 

で行えます。

簡単ですね。

最後に、自動起動を設定する際は、

sudo systemctl enable hello.service 

で行い、逆に自動起動を停止する際は

sudo systemctl disable hello.service 

で行えます。

非常に簡単ですので、覚えておいて損はないでしょう。

ではでは。

RaspberryPiでRAMディスク化する方法

RaspberryPiはLinuxで動いていますが、突然電源を切ってしまうと、ディスクを壊したりOSが起動しなくなる。
そんな問題が起きたりします。

回避のために行った内容をまとめていきます。

こんにちは、北神です。

RaspberryPiは組込み用途を想定された作りをしていない為、所謂「電源のブチ切り」には対応していません。

しかし、次第に組込み用途で使用される事が増え、世界中のエンジニアがアイディアを出し合い、とても良い方法が提案されました。

それは、「overlayfs」です。

この機能は、OSやアプリケーションの設定を変更せずに、起動時は、ディスクをRO(リードオンリー)に設定し、
起動後にアプリが書き込むファイルは、RAM上でRW(リードライト)にする機能です。

もう少し詳しく話しますと、

フォルダのアドレスをRAMやtmpに変更する必要がなく、書き換えや保存も”見かけ上”変更なく同等に実行されます。

しかし、見かけ上問題がないだけで、内部では書き換えや保存はRAM上で行われる為、保存ができたと思っても再起動をしますと保存前に戻ります。

さらに、試して気が付きましたが、書き込みがRAM上で行われる為、起動完了までの時間が少し速くなる事がわかりました。

ただ、ログ等の書き込みがRAMで行われてしまう為、電源を抜くと確認しに行く事ができない事を留意してください。

早速、そのoverlayfsの設定方法ですが

コマンドにて

sudo raspi-config nonint enable_overlayfs

これだけです。
これだけで、Rebootを行うと、起動時にROで立ち上がります。
書き込みや保存は全てRAMに転送されますので、保存されません。
これにより、電源ブチ切りを行っても、SDカードのディレクトリに障害は発生しません。
起動失敗による障害が減ると予想されます。

また、再びディスクに書き込みや保存を行いたい際は

sudo raspi-config nonint disable_overlayfs

を行い、再起動後に書き換えができます。

このoverlayfsに加え、systemd(systemctl)を駆使する事で、組込み用途で使用できるRaspberryPiに変化させる事できます。

ではでは。