2026年8月26日水曜日

Arduino手習い(WiFi WEBサーバ)

 ESP32でLEDチカチカは出来た。次は何しようか? 折角 ESP32-DevKitC-32E にはWiFiが内蔵されているからサーバとして動かしたいな。という事でネットを漁ってWiFiで動かすサーバの事例を調べた。すると250+ ESP32 Projects, Tutorials and Guides with Arduino IDE というホームページに Rui Santos 氏作成の手頃な例があったので、これを動かしてみた。

このプログラムはWiFiルータ経由でサーバとして機能し、LEDチカチカで使ったLEDをON/OFFするものだ。

プログラムの中には自分のWiFiルータのSSIDとパスワードを設定する必要があり、またLEDの接続されているピン番号を合わせる必要がある。このプログラムをコンパイル後ESP32に書き込んで起動するとIDEのシリアルモニタに、

WiFi connected.
IP address: 
192.168.xx.xxx 

と表示され、WiFiルータへ接続され、IPアドレスが割り振られたことが分かる。なおシリアルモニターを表示するメニューはIDEのツールメニューの中にある。またシリアルモニターではプログラムに合わせて通信速度を設定する必要がある。通信速度はプログラム中の次の記述で分かる。

Serial.begin(115200); 

このサーバへアクセスするにはWiFiルータへ接続されているPCや携帯のブラウザからESP32へ割り振られたIPアドレスにアクセスすればよい(このときユーザ名やパスワードは必要ない)。すると次のような画面が表示される(オリジナルのプログラムには2つのボタンがあったがLEDが1つしかないため2個目のボタンは表示されないようプログラムを修正した)。

 ここでONのボタンを押すとLEDが点灯するとともにボタンがOFFの表示に変わる。そしてこのOFFを押すとLEDが消灯しボタンはONに変わる。実際に携帯からアクセスすると少しもたついた感じの動きだったがPCからアクセスするとサクサク動いたので動作に問題はないようだ。

ソースコードは次の通り。

 /*********
  Original by Rui Santos
  Complete project details at http://randomnerdtutorials.com  
  Modified by MYcrosLip
*********/

// Load Wi-Fi library
#include <WiFi.h>

// Replace with your network credentials
const char* ssid = "xxxxxxxxxxx";   //"REPLACE_WITH_YOUR_SSID";
const char* password = "yyyyyyyyy"; //"REPLACE_WITH_YOUR_PASSWORD";

// Set web server port number to 80
WiFiServer server(80);

// Variable to store the HTTP request
String header;

// Auxiliar variables to store the current output state
String output5State = "off";
String output27State = "off";

// Assign output variables to GPIO pins
const int output5 = 5;     // 26;
//const int output27 = 18;    //27;

// Current time
unsigned long currentTime = millis();
// Previous time
unsigned long previousTime = 0; 
// Define timeout time in milliseconds (example: 2000ms = 2s)
const long timeoutTime = 2000;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  // Initialize the output variables as outputs
  pinMode(output5, OUTPUT);
  // Set outputs to LOW
  digitalWrite(output5, LOW);

  // Connect to Wi-Fi network with SSID and password
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  // Print local IP address and start web server
  Serial.println("");
  Serial.println("WiFi connected.");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
  server.begin();
}

void loop() {
  // put your main code here, to run repeatedly:
  WiFiClient client = server.available();   // Listen for incoming clients
  if (client) {                             // If a new client connects,
    currentTime = millis();
    previousTime = currentTime;
    Serial.println("New Client.");          // print a message out in the serial port
    String currentLine = "";                // make a String to hold incoming data from the client
    while (client.connected() && currentTime - previousTime <= timeoutTime) {  // loop while the client's connected
      currentTime = millis();
      if (client.available()) {             // if there's bytes to read from the client,
        char c = client.read();             // read a byte, then
        Serial.write(c);                    // print it out the serial monitor
        header += c;
        if (c == '\n') {                    // if the byte is a newline character
          // if the current line is blank, you got two newline characters in a row.
          // that's the end of the client HTTP request, so send a response:
          if (currentLine.length() == 0) {
            // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
            // and a content-type so the client knows what's coming, then a blank line:
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println("Connection: close");
            client.println();
            
            // turns the GPIOs on and off
            if (header.indexOf("GET /5/on") >= 0) {
              Serial.println("GPIO 5 on");
              output5State = "on";
              digitalWrite(output5, HIGH);
            } else if (header.indexOf("GET /5/off") >= 0) {
              Serial.println("GPIO 5 off");
              output5State = "off";
              digitalWrite(output5, LOW);
            }
            // Display the HTML web page
            client.println("<!DOCTYPE html><html>");
            client.println("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
            client.println("<link rel=\"icon\" href=\"data:,\">");
            // CSS to style the on/off buttons 
            // Feel free to change the background-color and font-size attributes to fit your preferences
            client.println("<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}");
            client.println(".button { background-color: #4CAF50; border: none; color: white; padding: 16px 40px;");
            client.println("text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}");
            client.println(".button2 {background-color: #555555;}</style></head>");
            
            // Web Page Heading
            client.println("<body><h1>ESP32 Web Server</h1>");
            
            // Display current state, and ON/OFF buttons for GPIO 26  
            client.println("<p>GPIO 5 - State " + output5State + "</p>");
            // If the output26State is off, it displays the ON button       
            if (output5State=="off") {
              client.println("<p><a href=\"/5/on\"><button class=\"button\">ON</button></a></p>");
            } else {
              client.println("<p><a href=\"/5/off\"><button class=\"button button2\">OFF</button></a></p>");
            } 
            client.println("</body></html>");
            
            // The HTTP response ends with another blank line
            client.println();
            // Break out of the while loop
            break;
          } else { // if you got a newline, then clear currentLine
            currentLine = "";
          }
        } else if (c != '\r') {  // if you got anything else but a carriage return character,
          currentLine += c;      // add it to the end of the currentLine
        }
      }
    }
    // Clear the header variable
    header = "";
    // Close the connection
    client.stop();
    Serial.println("Client disconnected.");
    Serial.println("");
  }
}


 

 


2026年8月25日火曜日

Arduino手習い

 YouTubeで日本の電子部品店の歴史という動画が出ていた。見てみるとお馴染み秋月や共立と並んでSwitch Scienceが紹介されていた。私はSwitch Scienceは外資系の会社だと思っていたが日本人が興した日本の会社だ。細かい話は動画を見ていただくこととして、Switch ScienceがArduino日本導入の先駆者らしい。PICの次はArduinoかと考えて、試してみたくなった。Arduinoは秋月でも売っているがこれまで気にも留めていなかった。

 Arduinoについて資料を漁り開発環境(Arduino IDE)をインストールした。たまたま秋月で興味本位で買ってデッドストックになっていた ESP32-DevKitC-32E はそのままIDEで扱えるようなので、お決まりのLEDチカチカに挑戦した。

IDEに付属のスケッチ例の中の 01.Basic にあった blink を試してみた。スケッチとは Arduino IDEにおけるプロジェクトに相当すると考えればよいようだ。スケッチはC/C++で記述し、最上位にmain() ではなく setup() と loop() という関数を記述する。setup() は初期化部分を記述し、loop() は実行部分を記述する。これは、

void main(){
    setup();
    while(1) loop();
}

という関係だと考えれば良いのだろう。

実際にIDEで例にあるプログラム(blink)をコンパイルすると、PICのコンパイラと比べやたら遅い。それにお決まりのコンパイルエラーだ。 エラーは次の記述で発生した。

   pinMode(LED_BUILTIN, OUTPUT);

 この LED_BUILTIN が不明だと。確かにプログラム中の何処にも LED_BUILTIN は規定されていないし、#include 文も無い。ここで全くの初心者なら途方に暮れ、投げ出す所だろう。ネットで pinMode を検索すると第一引数はピン番号を表す整数のようだ。ここに直接数字を書いても良いが #define 文で LED_BUILTIN を定義するのがCのやり方だ。そして出来上がったのが次のプログラムだ。

#define LED_BUILTIN   5

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

static void loop_sub(unsigned int len1, unsigned int len2) {
  digitalWrite(LED_BUILTIN, HIGH);
  delay(len1);
  digitalWrite(LED_BUILTIN, LOW);
  delay(len2);
}

void loop() {
  loop_sub(200, 200);
  loop_sub(200, 200);
  loop_sub(200, 200);
  loop_sub(600, 200);
  delay(500);   
}
 

単なる LEDチカチカでは面白くないのでモールスでテスト信号を表す V(= ...-)と光るようにした。

しかし、Cでプログラムを書くためには文法は勿論として、ライブラリーや関数に関する知識が不可欠だ。そこら辺の情報は何処にあってどう入手できるのだろうか。個人的には中身が見えないライブラリーは苦手だ。バグがあったり、メモリーを大量に消費したり、無駄なコードがあったり、見えない前提条件が設定されていたり、デバッグで苦労しそうな感が拭えないからだ。

しかしArduinoって名前は何を表しているんだ?製品名であり開発環境であるが、ESP32-DevKitC-32E も Arduino の範疇で扱える。Raspberry-Pi-Pico も Arduino IDE で扱えるらしい。なんか固定したイメージが無くぼんやりしていて良く分からない。まだまだ先は長そうだ。

(後日)

トラ技2025年10月号を見ると Arduino Uno にはオンボードLEDが搭載されており、それが13番ピンに接続されているらしい。その場合には LED_BUILTIN は規定値(13)として予め定義されているのだろう。それで Builtin か、納得。

 

2026年8月23日日曜日

超音波距離計(その6)

 車に取り付ける超音波距離センサーには防水機能が必須だ。一方秋月で売っていて(その1)で使った超音波センサー(US-015やHC-SR04)には防水機能が無く使い辛い。最近Amazonを覗いていたら防水機能を持った超音波距離センサーを売っていた。売っているタイプには2種類あり、1つはUS-015と同じような送信と受信が別のユニットになっている一体型のタイプで、他は車のバンパーについているような送受共用ユニット(トランジューサ)を用いたタイプである。送受共用ユニットを使った場合の特性を知りたかったのと値段的に随分安かったので、後者を注文した。

届いたセンサー(袋にはX0018RCNB3と記載)はトランジューサと制御基板が別になっていて、その間を2.5mのケーブルで接続する。制御基板の出力端子の印字を見てみると、電源のほかTrigとEchoがあり、またピン配置もUS-015と同じでピンコンパチに出来ているようだ。


さらにRX,TXという記載もある事から何処かのジャンパーを変更すればUARTにも対応しているのだろうが説明書は添付されてない。 

実際にこれを(その1)及び「LiDARを使った距離計(その2)」で作った基板に繋げてみると何もせずそのまま動作した。但しトランジューサが共用なので送受切替時間に対応したブラインドゾーンが大きく、19cm以下の距離は計れなかった。


 

2026年7月18日土曜日

LiDARを使った相対速度計(その9)

 PICを16F18326に変えることで、LiDARとの間の通信はI2Cに限定された。一方新品のLiDARのインターフェイスはデフォルトでUART(460,800bps)に設定されているのでこの矛盾を何とか整合させたい。以前、ポートを直接ソフトウェアでON/OFFしてUART信号を作り、460,800bpsを9600bpsに落とすことが出来たが、その応用として9600bpsのソフトウェアUARTを作りたい。とりあえず送信だけ出来れば何とかなるので作ってみた。そのコア部分は、

#define TX_OUT      LATAbits.LATA4

#define DLY_9600BPS __delay_us(100)     //Bit width shall be 104us 

static void send_byte_9600(byte data){

    TX_OUT      = 0;   DLY_9600BPS;  // Start bit
    byte cnt = 8;
    while(cnt-- > 0){
        TX_OUT  = data & 0x01;
        DLY_9600BPS;                // Data Bit
        data >>= 1;
    }
    TX_OUT      = 1; DLY_9600BPS; DLY_9600BPS;  // Stop bit  
}

void u2raw_send_cmd(byte *data, byte len){
    for(byte n=0; n<len; n++){
        send_byte_9600(*data++);
    }
}

である(同期ずれを防ぐため2StopBitsとしている)。また9600bpsに相当するビット幅は104μsだがオーバーヘッドを考慮して100μsの遅延を入れて試したら動いたので、そのまま使っている。なおこのプログラムを呼び出す前段階として出力ポートの設定や、同期ずれを防ぐための割り込み禁止措置が必要である。受信はサポートしていないので正確にはUAT(Universal Asynchronous Transmitter)だ。

これを使えばUARTでLiDARの一通りのコマンドを実行できる。完全なソフトウェアUARTを作る事もできるが、それは後回しにしよう。

とりあえず、ドライバーの立ち上がり時のLiDARの制御は、

  1. ソフトウェアUARTで460,800bpsを9600bpsへ落とす。
  2.  ソフトウェアUARTでインターフェイスをI2Cへ変更。
  3. 以降I2CインターフェイスでLiDARと通信。 

とする。2.のI2Cインターフェイスへの切り替えは次のようなコマンドをソフトウェアUARTで生成している。


LiDARは一旦I2Cへ切り替えると以降はUARTへ戻すコマンドを実行しない限り一旦電源を切ってもI2Cのまま保留されるが、I2C通信時にUART信号を送っても無視されるので実用上はこれで特に問題はない。

 (続く)

 

 

2026年7月14日火曜日

LiDARを使った相対速度計(その8)

 さて、PIC上で動かすI2Cのドライバー(プログラム)であるが、以前、見様見真似で納得しないまま作ったものを再利用してみたが上手く行かない。最初は何とか動いたように見えたが、ソフトを弄っているうちにLiDARが全く応答しなくなり、不正なデータを送って内部状態が異常になったか壊れたと思ってLiDARをもう1個買い足したりしたが結果は変わらなかった。

仕方が無いので改めてI2Cドライバーを作り直した。今回はハード/ソフトに関する理解が進んだのか意外とすんなりと動き、LiDARも正常な応答を返すようになった。

I2CでLiDARを扱う場合は先ずIDを読んでみて、IDを正しく読めているかを確認する。 I2Cの場合、データの書き込みより読み出しの方が少しややこしいのいで、読み出しが成功すれば一安心だ。実際のI2Cのトレースを取得してみると、

LiDARのIDの取得、 

 測距データの取得、

ソフトウェアバージョンの取得、
シリアル番号の取得、

レーザーON、

なおTSD20でレーザーをOFFにすると測距データは20,000mmに固定される。

 (続く)

 

2026年7月13日月曜日

LiDARを使った相対速度計(その7)

 TSD20は思いのほか小さかった。コネクタも小さいので抜き差しすると壊しそうだ。接続線も簡単に切れそうに細い。

TSD20では測定頻度を毎秒50回を含む他の頻度に変更するのも上手く行った。

 TSD20を空に向けると測定値は20000mmとなり、マニュアルにあるような50000mmでは無い。これはTSD50のマニュアル記述の使い回しで修正を失念したのかな。

LiDARにUARTでSTOPコマンドを送ると1秒1回程度の頻度で次のデータを自発的に送ってくる(6番目の0xf0はチェックサムだ)。 

5a 0f 02 ff ff f0

 これが何を意味しているのかマニュアルに記述が無く、ネット情報も無く皆目分からない。たぶん Keep Alive 的な性格を持っているものと推察する。

 

さて、TSD10やTSD20ではUARTインターフェイスで上手く行ったが、一方TSD50では毎秒500回という固定した測定頻度の関係で9600bpsではデータを送りきれない。そこでI2Cインターフェイスに挑戦する事にした。併せてPICを容量の大きいPIC16F18326に変えるが、このPICには2個目のUARTが搭載されていないのでI2C専用、或いはソフトでUARTを実現する必要がある。

PICとのインターフェイスでLiDARでデフォルトのUARTを選んだ場合、UARTは全二重通信なのでデータはLiDAR主導で測定頻度に従って勝手に送られてくる。またLiDARへのコマンドの送信とレスポンスの受信も送信側の勝手なタイミングで行われる。一方、I2Cの場合はポーリング方式なのでマスター(PIC)側主導でデータを転送する必要がある。

従ってUARTではPICはLiDARから勝手に送られてくるデータを受信し処理すればよいが、一方I2Cを使う場合はPIC主導で定期的にデータを取得し、その結果を処理する必要がある。つまりUARTとI2Cではソフトの造りが違ってくる。

I2Cを使ったデータ転送は具体的にはLiDARの内部レジスタのデータの読み出し/書込みという操作である。I2Cを使う場合は、LiDARの内部レジスタの書き換えは500Hzの測定毎に行われると思われるが、それとデータ取得の関係(タイミング)についてデータシートには何も説明が無く、やってみないと結果は分からない。

LiDARのI2Cアドレスについてもマニュアルの説明では0x52と書いてあるが説明図では0x54(0b010101xx)みたいに見える。これも試してみないと分らないが、後のチェックで0x52が正解であることが分った。

(続く)

  

2026年6月12日金曜日

LiDARを使った相対速度計(その6)

前にLiDAR距離計で作った基板では TSD20やTSD50に対応できないので、新しく基板を起こすことにした。TSD20は電源電圧が3.3Vであり、TSD20やTSD50ではUARTの代わりにI2Cインタ-フェイスを使うことができるので、それらを加味して次の回路を設計した。なおPICには16F15325を使っているがメモリー容量が足りないは場合はピンコンパチで2倍のメモリー容量を持つ16F18326を使う。ただし16F18326にはUARTは1つしか搭載していないのでLiDARインターフェイスはI2Cに限定される。

そして次の基板を発注した。部品点数は増えたがSMDを裏面にも配置して基板のサイズは前のままである。

1週間を待たず基板が届き、早速組立てた。

センサーにはTSD50を試したい所であるが、高価なうえに上手く行くとも限らないので、とりあえずより安価なTSD20で試す事とした。

(続く) 

(追伸)

上の回路図でI2Cバス用双方向電圧レベル変換IC(PCA9306D)とPICの間のI2Cの信号線にもプルアップ抵抗が必要です。