karakirimuのアイコン画像
karakirimu 2021年02月14日作成 (2021年02月14日更新)
製作品 製作品 閲覧数 1797
karakirimu 2021年02月14日作成 (2021年02月14日更新) 製作品 製作品 閲覧数 1797

赤外線送信+温湿度計のローカル接続デバイスの作成

赤外線送信+温湿度計のローカル接続デバイスの作成

はじめに

ESP32を利用して赤外線送信をローカルのWebサーバーからできるようにしてみました。元々Raspberry Pi 3bにて個人的に製作し、1年ほど運用しておりましたが、3bでこの機能を継続することは役不足と感じ、別のマイコンを利用しての再構築を考えていました。また、冬になり暖房で肌が乾燥してきたため、追加で部屋の湿度を知りたいと思っていました。そこで、温湿度計(DHT11)を追加してみました。

完成品

3Dデータ

概要

大まかな構成はこんな感じなります。ユーザーはブラウザからIRコードの送信や温湿度計の情報の受信を行います。ESP32 DevKit CはWiFiへ接続してサーバーIPの取得、SDカードからの各種情報の読み取り、温湿度計の情報の取得、ブラウザのボタンに対応する赤外線コードの取得、送信を行います。IRコードの追加、削除やSSIDの変更など、変更頻度の高いデータをすべてSDカードに集約しています。

構成

回路

以下の材料を使用しました。

  • ESP32 DevKit C x1
  • DHT11 x1
  • ユニバーサル基板 x1
  • LED取付基板 x1
  • 1K抵抗 x1
  • 10K抵抗 x1
  • 33K抵抗 x4
  • 33Ω抵抗 x7
  • 940nm赤外線LED(OSI5LA5A33A-B) x7
  • microSDモジュール x1
  • 2SD2012 (トランジスタ) x1

図のように結線します。センサとSDは3.3V、LEDは5Vへ接続します。

回路図

完成した基板(表)

完成した基板(裏)

温湿度計

回路図左上のDHT11です。IOポートは電源電圧と同じ3.3Vでプルアップします。

microSDモジュール

SPIで接続します。信号線(CS,CLK,MOSI,MISO)をすべて3.3Vでプルアップします。

赤外線LED

赤外線LEDの駆動回路です。赤外線LEDは940nmのものを使い、33Ωの抵抗を並列に配線しています。

プログラム

プログラムはArduino IDEで書き込むものと、SDカードに配置するhtml,js,cssの2つあります。ESP32とクライアント(ブラウザ)との主な送受信処理はこんな感じになります。

温湿度計

赤外線送信

Arduino IDEのコード

使用するライブラリ

プログラムは以下のライブラリを使っています。最初にインストールが必要です。

  • Arduino Json 6.17.2
  • DHT sensor library for ESPx 1.17.0
  • SDFat 2.0.4
  • IRremoteESP8266 2.7.14

コード

Arduino のコードは少し長めなので、説明はコード内に記述しました。

ESP32に書き込むコード

#include <WiFi.h> #include <ArduinoJson.h> #include <SdFat.h> #include <IRsend.h> #include "DHTesp.h" // 4KBキャッシュ const uint16_t SD_LOAD_BUFFERSIZE = 4096; // 赤外線LEDの出力ピン const uint8_t GPIO_IR_OUT_PIN = 4; // 温湿度計の入力ピン const uint8_t GPIO_DHT11_PIN = 22; // SDのSPI選択ピン const uint8_t CHIP_SELECT_SD = 5; // 赤外線データフォルダ const char* IR_JSON_DIR = "data/"; // サーバーの応答タイムアウト時間(ms) const long timeoutTime = 5000; // SDFatライブラリ SdFat SD; // IRremote (送信) IRsend irsend(GPIO_IR_OUT_PIN); // DHTXXの温湿度計センサライブラリ DHTesp dht; // ウェブサーバーをポート80に設定 WiFiServer server(80); // 現在の起動経過時間 static uint32_t currentTime = millis(); // 1つ前の起動経過時間 static uint32_t previousTime = 0; /** * ブラウザへの応答タイムアウト判定 */ bool timeout(){ previousTime = currentTime; return (timeoutTime < (currentTime - previousTime)); } /** * 温湿度計センサの値を取得 */ String getSensorValue(){ TempAndHumidity values = dht.getTempAndHumidity(); // 失敗した場合、空の文字列を返却 if (dht.getStatus() != 0) { Serial.println("DHT11 error status: " + String(dht.getStatusString())); return ""; } Serial.println(" T:" + String(values.temperature) + " H:" + String(values.humidity)); // {"temperature":数値,"humidity":数値} を返す return ("{\"temperature\":" + String(values.temperature) + ", \"humidity\":" + String(values.humidity) + "}"); } /** * 取得したURLに応じたファイルを読み込み、ブラウザへ * 応答を返す。 */ void loadUrl(String url, WiFiClient *client){ // SDからの読み込みバッファを確保 char readBuffer[SD_LOAD_BUFFERSIZE] = {0}; unsigned short counter = 0; File file = SD.open(url, FILE_READ); if (!file){ // 失敗時は下の文字列がブラウザに表示される。 client->println("<p>File could not load<p>"); return; } // ファイル読み込み while (file.available()) { readBuffer[counter] = file.read(); counter++; if(counter == SD_LOAD_BUFFERSIZE){ // バッファが最大に達した場合、書き込み (応答を返す) client->write(readBuffer, SD_LOAD_BUFFERSIZE); counter = 0; } } // 最後に読み込んだバッファを書き込み (応答を返す) if(counter > 0) client->write(readBuffer, counter); file.close(); // 送信は最後に空行が必要 client->println(); } /** * ヘッダー内からURLを抽出 */ String parseUrl(String *header){ unsigned int first = header->indexOf("GET "); unsigned int last = header->indexOf(" HTTP/1.1"); if (first >= 0 && last >= 0 && last > first) { return header->substring(first + 4, last); } return ""; } /** * ブラウザのリクエストが赤外線コードの送信要求かどうか判定 * フォーマット: ?sig=<デバイス名>&<機能> */ bool parseIrFunc(String url, String *device, String *func){ unsigned int first = url.indexOf("?sig="); unsigned int second = url.indexOf("&"); if (first >= 0 && second >= 0 && second > first) { *device = url.substring(first + 5, second); *func = url.substring(second + 1); return true; } return false; } /** * ブラウザのリクエストがセンサの更新要求かどうか判定 * フォーマット: ?req=<センサ名>&<時刻> */ bool parseSensorUpdate(String url, String *device){ unsigned int first = url.indexOf("?req="); unsigned int second = url.indexOf("&"); if (first >= 0 && second >= 0 && second > first) { *device = url.substring(first + 5, second); return true; } return false; } /** * ブラウザからの要求に対して応答を返す。(レスポンスヘッダー) */ void sendContentType(String type, WiFiClient *client){ client->println("HTTP/1.1 200 OK"); client->println("Content-type:" + type); client->println("Connection: keep-alive"); client->println(); // 最後に空行が必要 } /** * ヘッダーの1行分のバッファを取得 */ String readLine(WiFiClient *client){ String cline = ""; char ch; while (client->connected()) { ch = client->read(); if (ch == '\n') { break; } else if (ch != '\r') { cline += ch; } } Serial.println("Header : " + cline); return cline; } /** * データの種類を取得 */ String parseDataType(String path) { String dataType = "text/plain"; if (path.endsWith("/")) { path += "index.html"; } if (path.endsWith(".htm") || path.endsWith(".html")) { dataType = "text/html"; } else if (path.endsWith(".css")) { dataType = "text/css"; } else if (path.endsWith(".js")) { dataType = "application/javascript"; } else if (path.endsWith(".ico")) { dataType = "image/x-icon"; } return dataType; } /** * 文字列を16進数に変換 */ uint32_t stringToHex(String num){ String candidate = num; candidate.replace("0x",""); candidate.toUpperCase(); unsigned int len = candidate.length(); unsigned int array_max = len - 1; uint32_t result = 0; char c; for(int i = 0; i < len; i++){ c = candidate.charAt(i); if (c >= '0' && c <= '9') { result |= ((c - '0') << (4 * (array_max - i))); } else if (c >= 'A' && c <= 'F') { result |= (((c - 'A') + 10) << (4 * (array_max - i))); } } Serial.print("[stringToHex] result : 0x"); Serial.println(result, HEX); return result; } /** * WiFiのSSIDとパスワードを取得 */ void getWifiKeys(String &ssid, String &password){ // readline buffer String readBuffer; char ch; // config.jsonを開く File file = SD.open("/config.json", FILE_READ); if (!file){ Serial.println("[Config] file could not load."); return; } while (file.available()) { ch = file.read(); if (ch == '\n') { DynamicJsonDocument doc(readBuffer.length() + 4011); DeserializationError error = deserializeJson(doc, readBuffer); // Test if parsing succeeds. if (error) { Serial.print("size: "); Serial.println(readBuffer.length(), DEC); Serial.print("deserializeJson() failed: "); Serial.println(error.c_str()); return; } const char* key = doc["ssid"]; const char *type = doc["password"]; ssid = key; password = type; readBuffer = ""; } else if (ch != '\r') { readBuffer += ch; } } } /** * 赤外線コードを送信 */ void sendIr(String &device, String &func){ // readline buffer String readBuffer; char ch; // 送信周波数を38kHzに設定 const uint8_t IR_SEND_FREQ = 38; // フォルダ名 + デバイス名.jsonを開く File file = SD.open(IR_JSON_DIR + device + ".json", FILE_READ); if (!file){ Serial.println("[SendIr] file could not load."); String out = "file : " + *IR_JSON_DIR + device + ".json"; Serial.println(out); return; } while (file.available()) { ch = file.read(); if (ch == '\n') { // JSONフォーマットのファイルを取得 DynamicJsonDocument doc(readBuffer.length() + 4011); DeserializationError error = deserializeJson(doc, readBuffer); if (error) { Serial.print("size: "); Serial.println(readBuffer.length(), DEC); Serial.print("deserializeJson() failed: "); Serial.println(error.c_str()); return; } // 送信コードのキーを取得 const char* key = doc["key"]; // ブラウザから送信されたキーと同じかどうか判定 if(func.equals(key)){ // キーのタイプ(NEC,生コード)を取得 const char *type = doc["type"]; Serial.print("type : "); Serial.println(type); if(strcmp(type, "raw") == 0){ JsonArray codejson = doc["code"].as<JsonArray>(); std::vector<uint16_t> code; for(JsonVariant v : codejson) { code.emplace_back(v.as<uint16_t>()); Serial.print(v.as<uint16_t>(), DEC); Serial.print(","); } Serial.println(""); // IR送信を実行 irsend.sendRaw(&code[0], codejson.size(), IR_SEND_FREQ); Serial.println("[sendIr] Send raw code"); }else if(strcmp(type, "nec") == 0){ // IR送信を実行 irsend.sendNEC(stringToHex(doc["code"].as<String>())); Serial.println("[sendIr] Send nec code"); } break; } readBuffer = ""; } else if (ch != '\r') { readBuffer += ch; } } } void setup() { Serial.begin(115200); // LEDの初期化 pinMode(GPIO_IR_OUT_PIN, OUTPUT); digitalWrite(GPIO_IR_OUT_PIN, LOW); // 温湿度計の初期化 dht.setup(GPIO_DHT11_PIN, DHTesp::DHT11); // SDカードの初期化 if (!SD.begin(CHIP_SELECT_SD, SD_SCK_MHZ(25))) { Serial.println("SD card initialize failed."); return; } // SDからWiFiのSSIDとパスワードを取得 String ssid, password; getWifiKeys(ssid, password); Serial.println("SSID : " + ssid); // Serial.println("Password : " + password); // WiFi接続を確立 WiFi.begin(ssid.c_str(), password.c_str()); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); // ローカルのIPアドレスの表示 Serial.print("WiFi connected. IP: "); Serial.println(WiFi.localIP()); // サーバー開始 server.begin(); } void loop(){ // クライアントの取得 (取得失敗対策のため、5回リトライ) WiFiClient client; int i = 0; for(; i < 5; i++){ client = server.available(); if(client) break; delay(50); } if(i == 5) return; Serial.println("Connection started."); // レスポンスヘッダーの読み取りに必要なバッファ String header = ""; String currentLine = ""; // 接続中 && タイムアウト時間を超えていない currentTime = millis(); while (client.connected() && timeout() == false) { currentTime = millis(); if (!client.available()) continue; currentLine = readLine(&client); header += (currentLine + '\n'); if (currentLine.length() == 0) { // ヘッダーを最後まで読み込んだ場合 String url = parseUrl(&header); sendContentType(parseDataType(url), &client); // センサ更新要求への応答 String sensor; if(parseSensorUpdate(url, &sensor)){ String response = getSensorValue(); client.println(response.c_str()); Serial.println("Response: " + response); break; } // 赤外線コードの送信 (応答は何でもいい) String device, func; if(parseIrFunc(url, &device, &func)){ Serial.println("Remote Device : " + device); Serial.println("Function : " + func); sendIr(device, func); client.println("OK"); break; } // ページ読み込みへの応答 if (url.endsWith("/")) { url += "index.html"; } loadUrl(url, &client); break; } } // 接続を終了する (ブラウザへの応答完了) client.stop(); Serial.println("Connection closed.\n"); }

SDカード内に配置するコード

SDカードには、UIとなる部分とESP32にデータを送信する部分を作成します。見た目はこんな感じになります。

ホーム画面

リモコン操作UI

使用するライブラリ

SDの読み込み速度やSPIFFSなどを利用することも想定し、軽量で簡単に十分なUIを作れるPure.cssを採用しました。

フォルダ構成

SDカードにはファイルを下のように配置しました。appフォルダがリモコン操作する機器を入れたものになります。cssフォルダはUIの補助、jsフォルダにはリモコン操作の実行処理やメニューバーの実装が含まれています。dataフォルダは赤外線送信のコードが含まれています。今回は、ダミーのコードを作成し、送受信のテストをしてみます。

ルートフォルダ
├ index.html (ホーム画面)
├ config.json (WiFi設定)
📁app
│ └ 📁dummy
│        └ index.html (赤外線送信選択UI)
📁css
│ ├ layouts.css (赤外線送信UIの調整)
│ ├ side-menu.css (サイドメニュー)
│ └ pure-min.css (Pure.css本体)
📁js
│ ├ ui.js (サイドメニュー)
│ └ signal.js (赤外線送信命令の送信)
📁data
    └ DUMMY.json (赤外線送信命令に紐づけられたデータ値リスト)

ホーム画面 (ルートフォルダのindex.html)

script部分のupdateSensorで温湿度計の更新を行っています。具体的には、10秒ごとにこの関数が定期的に実行され、結果を得るとhtml上のtemp,temp_text,humid,humid_textに該当するidの要素が更新されます。

updateSensor内で利用されているXMLHTTPRequestは、ESP32のサーバー上では動作します。PC上にあるファイルのhtmlを開いて実行した場合は、CORSというエラーが発生します。

index.html

<!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="description" content="IR send server for ESP32"> <title>Remote Panel</title> <link rel="stylesheet" href="./css/pure-min.css"> <link rel="stylesheet" href="./css/side-menu.css"> <link rel="stylesheet" href="./css/layouts.css"> <script> window.onload = function (){ updateSensor('dht11'); } function updateSensor(device) { var xhr = new XMLHttpRequest(); xhr.timeout = 2000; xhr.responseType = 'text'; xhr.onload = function (e) { if (xhr.readyState === 4) { if (xhr.status === 200) { console.log(xhr.responseText); var tempprogress = document.getElementById('temp'); var temp = document.getElementById('temp_text'); var humidprogress = document.getElementById('humid'); var humid = document.getElementById('humid_text'); // 結果を読み取り const parsed = JSON.parse(xhr.responseText); tempprogress.value = Number(parsed.temperature); temp.innerHTML = Number(parsed.temperature).toFixed(1) + ' ℃'; humidprogress.value = Number(parsed.humidity); humid.innerHTML = Number(parsed.humidity).toFixed(1) + ' %'; } else { console.error(xhr.statusText); } } }; xhr.ontimeout = function(e) { xhr.abort() ; } xhr.open('GET', '?req=' + device + '&' + new Date().getTime(), true); xhr.setRequestHeader('Content-type', 'application/json'); xhr.setRequestHeader('Cache-Control', 'no-cache'); xhr.setRequestHeader('X-Content-Type-Options','nosniff'); xhr.send(); } window.addEventListener('load', function () { // 10秒間隔で更新を設定 setInterval(function () { updateSensor('dht11'); }, 10000); }); </script> </head> <body> <div id="layout"> <!-- Menu toggle --> <a href="#menu" id="menuLink" class="menu-link"> <!-- Hamburger icon --> <span></span> </a> <div id="menu"> <div class="pure-menu"> <a class="pure-menu-heading" href="#">HOME</a> <ul class="pure-menu-list"> <!-- ボタン定義 --> <li class="pure-menu-item"> <a href="/app/dummy/" class="pure-menu-link">テスト</a> </li> </ul> </div> </div> <div id="main"> <div class="content"> <h2 class="content-subhead">部屋</h2> <div class="pure-g room"> <!-- disp 1 --> <div class="pure-u-1-2"> <label for="temp">温度</label> <meter style="width: 10vw; max-width: 100px;" id="temp" min="-20" max="80" low="10" high="30" optimum="25" value="20"> </meter> </div> <div class="pure-u-1-2"> <div id="temp_text"> - ℃</div> </div> </div> <div class="pure-g room"> <!-- disp 2 --> <div class="pure-u-1-2"> <label for="humid">湿度</label> <meter style="width: 10vw; max-width: 100px;" id="humid" min="0" max="100" low="33" high="66" optimum="80" value="90.8"> </meter> </div> <div class="pure-u-1-2"> <div id="humid_text"> - %</div> </div> </div> <h2 class="content-subhead">デバイス</h2> <div class="pure-g"> <div class="pure-u-1-2"> <button class="index-button pure-button" onclick="location.href='/app/dummy/';">テスト<br>(Dummy)</button> </div> </div> </div> </div> </div> <script src="js/ui.js"></script> </body> </html>

Wifi設定 (ルートフォルダのconfig.json)

””の中を置き換えて使用します。

config.json

{"ssid":"SSID","password":"パスワード"}

jsonを読み込むためには、行の最後に改行が必要です。

赤外線送信選択UI (app/dummy/index.html)

ボタンでsendSignalを呼び出すようにして、後述のsignal.jsに定義されたsendSignalBaseを通してESP32にリクエストを送信します。

app/dummy/index.html

<!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="description" content="IR send server for ESP32"> <title>Remote Panel</title> <link rel="stylesheet" href="../../css/pure-min.css"> <link rel="stylesheet" href="../../css/side-menu.css"> <link rel="stylesheet" href="../../css/layouts.css"> <script src="../../js/signal.js"></script> <script>function sendSignal(key){ sendSignalBase('DUMMY', key); }</script> </head> <body> <div id="layout"> <!-- Menu toggle --> <a href="#menu" id="menuLink" class="menu-link"> <!-- Hamburger icon --> <span></span> </a> <div id="menu"> <div class="pure-menu"> <a class="pure-menu-heading" href="../../index.html">HOME</a> <ul class="pure-menu-list"> <!-- ボタン定義 --> <li class="menu-item-divided pure-menu-item pure-menu-selected"> <a href="#" class="pure-menu-link">テスト</a> </li> </ul> </div> </div> <div id="main"> <div class="content"> <div class="pure-g"> <!-- row 1 --> <div class="pure-u-1-3"> <button class="button-green pure-button" onclick="sendSignal('KEY_1 ')">テスト1</button> </div> <div class="pure-u-1-3"> <button class="button-lightgray pure-button" onclick="sendSignal('KEY_2')">テスト2</button> </div> <div class="pure-u-1-3"> <button class="button-lightgray pure-button" onclick="sendSignal('KEY_3')">テスト3</button> </div> <!-- row 2 --> <div class="pure-u-1-1"> <p style="text-align: right"><br>Dummy</p> </div> </div> </div> </div> </div> <script src="../../js/ui.js"></script> </body> </html>

UI調整ファイル (css/layouts.css)

センサ部分の表示サイズの設定と、ボタンの色設定です。

layouts.css

.room { width: 100%; height: 100%; margin: 0.5em; color: white; font-size: 1rem; border-radius: 4px; text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); text-align: center; } .index-button { width: 98%; height: 90%; padding-bottom: 0.3em; margin: 1em; border-radius: 4px; text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); background: rgb(200, 200, 200); /* this is a gray */ } .button-green, .button-lightgray { width: 98%; height: 90%; padding-bottom: 0.3em; margin: 0.5em; color: white; font-size: 0.6rem; border-radius: 4px; text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); } .button-green { background: rgb(28, 184, 65); /* this is a green */ } .button-lightgray { background: rgb(200, 200, 200); /* this is a light gray */ color: black; }

サイドメニュー (css/side-menu.css)

https://purecss.io/layouts/side-menu/の説明にこのファイルへのリンクがあります。このコードをコピーし、以下の部分を改変します。

  • bodyのかっこ内にbackground-color: #2c2c2c;を追加

  • #menuかっこ内に-webkit-overflow-scrolling: touch;の追加

  • #menu .pure-menu-selected, #menu .pure-menu-heading{}の部分を以下に変更

    #menu .pure-menu-selected {
    	background: #1f8dd6;
    }
    
    #menu .pure-menu-heading {
    	background: transparent;
    }
  • .menu-link span,.menu-link span:before,.menu-link span:after {}部分のpointer-events: none;の削除

Pure.css本体 (css/pure-min.css)

公式サイトのリンクを辿って中身をそのままコピーしてきます。

https://purecss.io/

サイドバー選択処理 (js/ui.js)

https://purecss.io/layouts/side-menu/ の説明にリンクがあるので、本体をコピーしてきます。

リモコン送信処理 (js/signal.js)

sendSignalBaseを呼び出すと、ESP32側に現在のパス+?sig=デバイス名&送信リモコンキー名が送信されます。

signal.js

function sendSignalBase(device, key){ var xhr = new XMLHttpRequest(); xhr.open('GET', '?sig=' + device +'&' + key ); xhr.timeout = 1000 ; xhr.setRequestHeader('Cache-Control', 'no-cache'); xhr.responseType = 'document' ; xhr.ontimeout = function(e) { xhr.abort() ; } xhr.send(); }

リモコン送信コード (data/DUMMY.json)

送信コードです。今回はNECと生コードで実験します。コードの説明を次に示します。

キー名 値の説明
type 値の種類 (rawかnec)
key ブラウザから送られるキー
code 赤外線コードの値

DUMMY.json

{"type":"nec","key":"KEY_1","code":"0x12345678"} {"type":"nec","key":"KEY_2","code":"0x56781234"} {"type":"raw","key":"KEY_3","code":[9000,4500,560,350,560,560,560,560,560,560]}

jsonを読み込むためには、行の最後に改行が必要です。

実験

ArduinoのIRremoteのサンプル、IRrecvDumpをそのまま利用して、Arduino Leonardoで今回作成したデバイスの送信コードを受信してみます。実験結果は下の通りで、NECのコードは一致し、Rawのコードはタイミングが少々違いますが、赤外線リモコンとしては許容範囲であると思います。(受信ICは秋月のSPS-442-1です。)

Ready to receive IR signals at pin 11
12345678
Decoded NEC: 12345678 (32 bits)
Raw (68): 9050 -4400 650 -500 650 -450 600 -550 650 -1600 650 -500 600 -500 650 -1600 650 -500 550 -550 650 -500 600 -1700 550 -1600 650 -500 600 -1650 600 -500 650 -500 600 -550 600 -1600 600 -550 600 -1650 600 -500 650 -1650 600 -1600 600 -500 650 -500 600 -1650 600 -1700 550 -1650 600 -1650 550 -550 650 -500 600 -500 600 
56781234
Decoded NEC: 56781234 (32 bits)
Raw (68): 9050 -4400 650 -500 650 -1550 700 -450 650 -1600 650 -450 650 -1600 650 -1600 650 -500 650 -450 650 -1600 650 -1600 650 -1600 650 -1600 600 -500 650 -500 600 -500 650 -500 600 -500 650 -500 600 -1650 600 -500 650 -500 600 -1650 600 -500 650 -500 600 -500 650 -1600 650 -1600 600 -550 600 -1650 600 -500 650 -500 600 
9D334F57
Unknown encoding: 9D334F57 (32 bits)
Raw (10): 9100 -4400 600 -300 700 -450 650 -450 700

実験の様子

おわりに

  • SPIFFSでアップロードする話が少し出てきたと思いますが、開発中だったコードをアップロードして試すと結構速度が遅かったので、SDを採用することになりました。
  • ブレッドボードで試すと接触のせいか、時々SDが読めなかったりします。
  • たまにレイアウトが崩れます。ブラウザ更新すれば直るのですが、原因はまだわかりません。
1
ログインしてコメントを投稿する