2026年9月13日日曜日

Arduino手習い(WiFi WEBサーバ、気象データ(BME280)の表示)

 動的なWEBサーバを構築するに当たって、リアルタイムデータが欲しい。ネットで事例を当たっていると1個で気圧・温度・湿度が図れるBME280というのセンサーが便利なようだ。そういえば似たようなセンサー昔秋月で買ったことがあるぞ、と過去の未完成基板を引っ張り出してみたら上手い具合にBME280モジュールが載っていた。

早速、簡単な事例を探して動かしてみたら必要なライブラリーのインクルードと若干の修正で簡単に動いた。

PCからアクセスすると次のような表示になる。 

この元となったブログは「すっさんぽ」さんの、ESP32とBME280で、温湿度計を作った、だ。

このプログラムはネット経由で時刻を取得するのでESP32スタンドアローンでは動かせないが、一方mDNSの機能を搭載し、IPアドレスではなくホスト名(esp32.local)でアクセスできる所が優れている。

データは1分毎に更新されるが、それはクライアント側からリロードのリクエストを出す事で実現されているので、最終的に目標としているPUSH型ではない。 

ちなみに、ソースコードの変更点は次の通り。

  • 漢字コードがShift-JISだったのでUTF-8に変更
  • HTMLの定義の中の 
    <title></title>

    <title>気温・湿度・気圧(BME280)</title>
    に変更(ブラウザのタブにタイトルが表示されるようになる)。
  • シリアル通信が初期化されていなかったので
    void setup() {
    の次の行に
    Serial.begin(115200);
    を挿入。
  •  setup()の最後に
    Serial.println("ESP32 Web Server(+BME280) Ready!");
    を挿入。   

 以上、

2026年9月12日土曜日

フォノイコライザーを作る(その2)

 基板が製作されている間に秋月へ部品を調達に行った。八潮店に一通りの部品は揃っていたが、2セット分を買ったので物によっては在庫数が少なく全部買い占めた物や足りない物もあった(数日中に補填されるようだが)。今回作る回路は次の通り(回路図にあるOPアンプはパターンを作るため適当に選んだもので実際には前回説明した物を使う)。

部品を買ってみて特に驚いたのはオーディオ用無極性電解コンデンサー220u25Vが思いのほか大きいことだ。直径が1cmと大きい事は基板設計時に分かっていたが高さも25mmあり設置条件に影響する。ひょっとしたら無極性にするために440uFを2個直列にしているのかな?次の写真は今回買ったもので左から10uF50V、220uF25V、470uF35V。10uFと220uFはオーディオ用無極性(MUSE・ES)、470uFは普通の電解コン。

ちなみにこの220uFのコンデンサーは直流ゲインを0dBに落とすための物なのでOPアンプのバランスが良ければ、或いは重低音を重視する場合は不要で空いた端子は短絡(ショート)すればよい。この場合、直流ゲインは60dB(1000倍)になる。ちなみにこのコンデンサと直列抵抗(270Ω)によるカットオフ周波数は約3Hzだ。

(続く)

 

 

2026年9月7日月曜日

フォノイコライザーを作る

 知人より、古いレコードプレーヤをオーディオアンプに繋ぎたいという相談が来た。オーディオアンプにはフォノ端子が付いていなくてLine入力に入れてボリュームを最大にしても小さな音しか出ないそうだ。音質に拘りは無く普通に音が出れば良いようだ。

レコードプレーヤをフォノ端子の無いオーディオアンプに繋ぐためにはRIAA特性を補正できるフォノイコライザーを入れる必要がある。中華製の安いフォノイコライザーを売っているサイトもあるが特性的に怪しく評価が低いそうだ。プレーヤの内部に組み込めるようなものを作って欲しいとの事なのでネットで回路を拾って基板を起こしてみた。

参考にした回路は次のWEBページだ。RIAA特性を構成するには中途半端な値の部品を使う場合が多いが次のWEBページでは出来るだけ普通の系列の値に合わせてある。

OPアンプやコンデンサには秋月で買える範囲で評判の良い部品を使うつもりだ。OPアンプにはMUSESシリーズが評判が良いようだが値段が張る。代わりに手頃な価格のNL8902ANを使う事とした。部品代を見積もってみたが電源込みで2千円を超える。それに今回からPCB製造料や送料が約2倍に跳ね上がっていた。
 
RIAAイコライザーは入力電圧が数mV程度であるうえに低音のゲインが60dB近くありハムノイズを拾い易い。FET入力でインピーダンスの高いOPアンプを使うので(NL8902ANはFET入力)なおさらだ。以前、オシロスコープのプローブを解放した状態で室内蛍光灯下に置いたら、それだけで数mVのノイズが観測できた。室内のノイズは思いのほか大きいようだ。
 
私がこれまでの制作した基板はデジタル系が主で殆どノイズを気にする必要は無かったが、今回初めてアナログ系の基板を設計した。今回設計したアナログ系は(デジタル系と違って)配線が短く簡単だった。配線を考える上ではGNDを分離するなどノイズにも気を使ったつもりだが結果は出来上がった基板を組み立てて音を出してみるまで分からない。いや、我が家にはレコードプレーヤは無いので誰かに評価してもらうか測定器で測る事しか出来ないが、結果はどうだろうか。

(続く) 

 

2026年8月30日日曜日

Arduino手習い(WiFi AP WEBサーバ)

 前回作ったプログラムはWiFiルータ経由でサーバとして機能するように出来ているので、1:1で直接携帯と通信する事は出来ない。そこを何とか出来ないかと調べてみた。そのようなサーバを構築するにはESP32がアクセスポイント(AP)として機能する必要があるとともにDHCPなど必要な機能を追加する必要がある。

何か良い例は無いかと探していたら「勝手な電子マン」さんのBlog「ESP32のアクセスポイント (つまりスタンドアローンWiFi-LAN)」を見つけた。

このBlogのsetup()部分を参考に、loop()は部分は前回のサーバプログラムを踏襲してプログラムを組んでみたら。ほとんど修正無く動いた。

使い方は、

  1. 先ずこのプログラムを書き込んだESP32を起動しサーバを立ち上げる。
  2. 次に携帯のWiFiの設定を開き、このプログラムで設定したSSID(ESP32-AP)に接続する(このときプログラム中で設定したパスワードが必要)。

  3. 携帯のブラウザを開きサーバのIPアドレスに接続すると前回と同じ画面が表示される。

 このとき携帯に割り振られるIPアドレスはサーバのIPアドレス+1だった。

とりあえず動いたので、setup()までのソースコードを紹介する。loop()部分は前回と同じなので省略した。 

/********************************************************
 * Original: Independent Access-Point - ESP32 simple example  
 *        Initial version V.00 Sept.26 2020
 *  https://a-tomi.hatenablog.com/entry/2020/09/27/212032
 *                                   by Akira Tominaga
 * Modified by MYcrosLip
********************************************************/
#include "WiFi.h"
const char ssid[]  = "ESP32-AP";  // *** set any ssid ***
const char pass[] =  "11223344";  // *** set any pw   ***
const IPAddress ip(192, 168, 11, 1);   // *** set any addr ***
const IPAddress subnet(255, 255, 255, 0);
WiFiServer server(80);
String header;

gpio_num_t LED = GPIO_NUM_5;
String output5State = "off";
const int output5 = 5;

unsigned long currentTime = millis();
unsigned long previousTime = 0;
const long timeoutTime = 2000;

void setup() {
  Serial.begin(115200);
  pinMode(LED, OUTPUT);  
  digitalWrite(LED, LOW);
   delay(100);
  WiFi.softAP(ssid, pass);
   delay(100);
  WiFi.softAPConfig(ip, ip, subnet);
  IPAddress myIP = WiFi.softAPIP();
  server.begin();
  Serial.print("SSID= ");
  Serial.println(ssid);
  Serial.print("Fixed IP addr= ");
  Serial.println(myIP);
  Serial.println("Server starting!");
}


 

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";

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

// 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("");
  }
}

(その後)

コンソールで確認しないとIPアドレスが分からないのはサーバーとして機能させるには不便だ。そこで固定のIPアドレスを割り振れないか調べたが、簡単だった。先ずIPアドレス型の定数(オブジェクト?)を次の様に宣言する。

const IPAddress ip(192, 168, xxx, xxx);
 

実際にこのIPアドレスを設定するには、

WiFi.config(ip);

を WiFi.begin(ssid, password); の前に置けばよい。

 以上、

2026年8月25日火曜日

Arduino手習い

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

 Arduinoについて資料を漁り開発環境(Arduino IDE、以下IDEとする)をインストールした。たまたま秋月で興味本位で買ってデッドストックになっていた ESP32-DevKitC-32E(以下 ESP32 とする) はそのまま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 か、納得。一方上記のプログラム中の OUTPUT、HIGH、LOW も同様に定義されていると考えられる。

 

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の信号線にもプルアップ抵抗が必要です。 

 

2026年5月29日金曜日

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

 出来上がった相対速度計を室内(廊下)でテストしてみたが、測定距離が4~5m位になると測定値が安定しなくなるというよりランダムな値が出るようになる。その原因はS/Nの悪化か、空中の埃やエアロゾルの影響か、或いは測定原理に起因するものか分からない。

そもそも赤外線が1nsで進む距離は30cmである。これを使ってmm単位の距離を測るためには数ps単位の時間分解能が必要であり、そのため初歩的な方法では100GHz以上のクロックが必要で現実的ではない。 またLEDやフォトダイオードの応答速度なども大きく影響しまともな測定は出来ないだろう。

この点をCopilotに相談すると波形の相関処理を行う事でサンプリング周期の100倍以上の分解能を得る事が出来るようだ。逆に考えると、何らかの要因で波形が乱れて相関処理が上手く行かなくなると極端に精度が悪くなるのだろう。これ以上測定範囲を伸ばしたいなら上位のLiDARセンサーを買う必要があろう。いずれにしても距離を正確に計れなければ相対速度もヘッタクレも無い。

 さて、相対速度計を実際の車に持ち込んで動かしてみた。赤外線がガラス窓に直交するようにLiDARを向けるとガラス窓の反射を拾って窓までの距離を測った。一方、赤外線がガラス窓に直交しないように置くと数十cm~数mの範囲の出鱈目な数字を表示した。ガラスの赤外線の減衰が大きいのかガラスを通しては正しく測定できないように見える。そこでLiDARを車外に出してみたが結果は変わらない。外光の影響が大きいのかな。とにかくこのままでは上手く行かない。

 上位のLiDARを使ってみる必要があるようだ。上位のLiDARには距離を20mまで計れるTSD20と50mまで計れるTSD50がある。どちらを使うにしてもTSD10と互換では無いので一工夫が必要だ。例えばTSD20は電源電圧が3.3Vだし、測定頻度が200回/秒だ。TSD50では電源は5Vでよいが測定頻度が500回/秒なので9600bpsではデータの転送が間に合わない。


何処をどうトレードオフしようか?

(続く)

 

2026年5月23日土曜日

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

 とりあえず基板を百均のクリアトレー(ミニ)を加工した箱に入れた。


LiDAR(TSD10)は反射物までの距離の測定を毎秒50回行う。前出のセンサー部はこの10回分を平均して、即ち0.2秒毎に距離をcm単位で報告してくる。

相対速度はこの0.2妙毎の距離の変化(差分)から算出すればよい。具体的には差分(m/0.2sec)を18000倍(=5×60×60)して100000で割ることで時速(Km/h)に変換できる。

7セグLEDの表示はこの相対速度(Km/h)と距離(cm)をスイッチで切り替えられるようにした。 またセンサー部からの入力が無い場合は全桁ドット(.)を、センサーの探知範囲外は全桁ダッシュ(-)を表示する。

左端の緑、黄、赤のLEDは次の様に点灯させる。緑は相対速度が正の場合に点灯、 赤は相対速度が負の場合に点灯、赤と緑の点灯には閾値を設定している。黄色は相対速度が0の状態が3秒以上続いた場合に静止状態と判断し点灯する。黄色から緑へ点灯が変化する場合、即ち信号待ちで停車中に前車が発射した場合にブザーをピッと鳴動させる。ブザーは当初は市販の小型電子ブザーを使ったが音がプアーだったのでタイマーで作った約2KHzの方形波でスピーカを鳴らすようにした。


次の写真で赤丸は 追加した相対速度と距離の切り替えスイッチ、橙丸は追加したダイナミックスピーカ。スピーカはPICの出力からコンデンサーで直流分を切って直接駆動している。


これで予定した基本的な機能は実装できたはずである。

(続く) 

 

2026年5月19日火曜日

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

 ソフトの制作と並行して基板を組み立てた。

そしてPICにソフトを書き込んでデータを表示させてみた。
実際にはTM-I2Cの実装とテストに思いのほか手間がかかった。しかし動き始めればこっちのものである。
あとは上位アプリをどう作って行こうか?
 
(続く) 
 
 

2026年5月12日火曜日

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

 今回の速度計で一番問題となるのはTM1637のI2Cモドキの2線インターフェイスだ(以降「TM-I2C」インターフェイスとする)。最初はPICのI2Cインターフェイスが流用できるかもしれないと考えたが仕様を検討して諦めた。TM-I2CもI2Cのようにクロックとデータの2線構成だが転送する最初のバイトがI2Cのアドレス+R/WではなくTM-I2Cはコマンドだ。またビットの並びも逆になっている(これはソフト的に解決可能だが)。従ってTM-I2Cはソフト的に実装する事とした。TM-I2Cの通信速度に下限規定は無いので実装は楽だろう。

TM-I2Cでデータ線は双方向通信を行う必要があるが、これはWired-OR接続で解決できる(注1)。Wired-OR接続の設定は例えばC1ポートでは次の様に設定した。 

    // 15: RC1 - DSPDAT
        LATCbits.LATC1   = 1;       // normal H
        TRISCbits.TRISC1 = 0;       // Output Enable
        ANSELCbits.ANSC1 = 0;       // Digital
        ODCONCbits.ODCC1 = 1;       // Open Drain
        WPUCbits.WPUC1   = 1;       // Weak Pull-Up

プログラム中ではデータ線、クロック線については次の様にマクロ定義している。また適当に遅延を入れてタイミングを調整している。伝送速度は10Kbps程度。

    #define CLK_OUT     LATCbits.LATC0
    #define CLK_IN      PORTCbits.RC0
    #define DAT_OUT     LATCbits.LATC1
    #define DAT_IN      PORTCbits.RC1

    #define DLY_4US     __delay_us(4)
    #define DLY_10US    __delay_us(10)
    #define DLY_50US    __delay_us(50)


 クロックが1の間データ線は変化してはならない(スタート・ストップ時を除く)から、1ビットの送信は、

    static void tm_send_bit(boolean d){
        DAT_OUT     = d;
        DLY_10US;
        CLK_OUT     = 1;
        DLY_50US;
        CLK_OUT     = 0;
        DLY_50US;
    }
で良いだろう。逆に1ビットの受信は、

    static boolean tm_rcv_bit(){
        boolean dat;
        DAT_OUT     = 1;
        DLY_50US;
        CLK_OUT     = 1;
        dat     = DAT_IN;
        DLY_50US;
        return dat;
    }
 スタートビット、ストップビットについては、

    static void tm_start_bit(){
        DAT_OUT     = 0;
        DLY_50US;
        CLK_OUT     = 0;
        DLY_50US;
    }

    static void tm_stop_bit(){
        DAT_OUT     = 0;
        DLY_10US;
        CLK_OUT     = 1;
        DLY_50US;
        DAT_OUT     = 1;
        DLY_50US;
        DLY_50US;
    }


としている。そして出来上がったTM-I2Cの波形をpulseviewで見ると、例えば次の様になった。ちなみにpulseviewのデコーダにはI2Cの物を使っているのでデコード結果は不正確だがデバッグには十分である。Ackの待ちは特に入れていないが通信速度が遅いので問題ないようだ。

(続く)

  〔注1〕この接続法はWikipediaを見るとWired-AND接続と書かれているが、初期の8ビットのコンピュータ時代、カードエッジコネクタを通して負論理バスで接続していた頃はWired-OR接続と呼んでいた。今回は信号は正論理だが動作はOR的なのでどちらの呼称が適切だろうか?これはIC-R6のCI-Vバスでも同じ事が言えるのだが。

2026年4月30日木曜日

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

 以前米国を走っているトラック野郎USAの動画を見ていたら、自車の車速に加えて前車の車速を測る機械が付いていて、あれは便利そうだなと思った。またAquaを運転し赤信号で止まっている時、前車が発車した後自車が発車せずにいるとピッと警告で知らせてくれる。これも便利だ。

そこで同じ様な機能が作れないかと考えてみた。超音波センサーは車外に出すセンサー部の防水が面倒なので除外する。前車の速度を測るにはミリ波レーダが良さそうだが距離を安価に測れるものは秋月では売ってない。 一方PONO社のLiDARは最大50mまで計れるし安全基準はCLASS1となっているのでこれを使うのが良かろう。とりあえず前に使っていた10mまで計れるTSD10を使ってみる事にする。上手く行ったら50mまで計れるTSD50も試してみよう。なおTSD20は電圧仕様が異なるため今回は除外する。

TSD10は10mまで、TSD50は50mまで計れるから4桁の表示器が欲しい。秋月で色々物色した結果4桁の緑色の7セグLEDを使う事とした(当初は4桁2色の7セグLEDを使うことも考えたが全幅が3cmと思いのほか小さかったため諦めた)。ドライバーには TM1637というアノードコモン用のドライバーを使う事とする。TM1637は外付け部品無しで直接6桁までの7セグLEDを扱うことが出来る(このICがある事は超音波距離計を作っている時は気付かなかった。知ってたらもっとコンパクトに出来たかもしれない)。またTM1637はI2Cに似た仕様の2線インターフェイスで接続するのでPICの足を無駄に消費せず便利だ。

以上を踏まえて早速基板を設計・発注した。回路図上はPICに20ピンの16F18346を使うように書いているが実際はそれとほぼ同じ内容で14ピンの16F18326を使うつもりだ。J5は汎用入出力用としており特に役目は決めていない。

 

ケースの高さを抑えるため基板は2つにカットし、L型に仕上げて使うようにしている。

(続く)

2026年4月20日月曜日

Windowsのショートカットとリンクの違い

 前の投稿で、Windowsのショートカットとリンクは違うようだと書いたが、その違いを実際に試してみた。

まず、実際に同一フォルダ(E:\CAR)のショートカットとリンクを作成してみた。リンクの作成は管理者権限のcmdで次のコマンドを実行した。

C:\work>mklink /D .\CAR-ln E:\CAR
.\CAR-ln <<===>> E:\CAR のシンボリック リンクが作成されました 

リンクの実体はシンボリックリンク、それならショートカットと同じはずだと考えたがエクスプローラの表示は異なっている。

 明らかな違いは、ショートカットには1KBと表示される実体(ファイル)が見えているがリンクにはそれが無く、あたかもそこにリンク先の実体(フォルダ)があるように表示されており、見かけ上UNIX(Linux)のリンクのように機能している。UNIXのシンボリックリンクならショートカットと同じく実体がある筈だが、Windowsではそれは隠されているか別のカラクリで実装されているのだろう。

各々のプロパティを見てみると、

となっている。やはり上で述べたようにリンクはシンボリックリンクではなくUNIXのリンクのように見えている。

リンクではリンク先のファイルが読み取り専用のように見えるがこれは変更できた。

 Windowsでショートカットとリンクは同じように見えてシステム上の扱いは別物だという事がわかった。Windowsには別のカラクリとしてジャンクションもあるようだしショートカットとリンクの細かい話もあるが、それらはCopilotにでも相談して欲しい。

 

〔参考〕UNIX(Linux)の リンクとシンボリックリンク

UNIXのファイルの実体は i-nodeというデータに1:1で紐づけられている。 i-nodeは i-node番号というユニークな番号を持っている。UNIXのディレクトリはファイル名と i-node番号の対応表にすぎない。従って1つの i-nodeに対応したファイル名を複数作ることができ、これがリンクである。各i-nodeにはこの実体がいくつの名前と紐づけられているかを表すリンク数というパラメータがあり、これが0になるとその実体は消去される。

UNIXのファイルはファイルシステムという単位で管理されている。UNIXのファイルシステムは、ストレージ内の管理構造という意味ではWindowsのドライブに相当するものと考えられるが、UNIXにドライブの概念は無く、UNIXのファイル全体は1本の木構造をしている。複数のファイルシステムを扱う場合、根元(root)から立ち上がった木の何処かの枝に他の木を接ぎ木(mount)する。

i-nodeはファイルシステム毎に独立しているため、ファイルシステムが異なるとリンクは出来ない。これを可能にするのがUNIXのシンボリックリンクであり、Windowsのショートカットと同様にパスを示してファイルやディレクトリ(フォルダ)を指定する。

 一方、私はWindowsのファイルについてFat32とかNTFSという名前は知っているが 内部構造は全く理解していない。

2026年4月10日金曜日

C:ドライブが満杯

 家族のPCの C: ドライブ(500GB)がほぼ満杯で動きが悪い(これ以上写真を保存できない)との相談を受けた。

どうせ写真や動画を撮りまくってるんだろうと思いながら内容を確認すると、C: ドライブの大部分は iTunes が作成した iPhone のバックアップファイルで、何と計286GB もあった。そのほか iTunes が使っていた一時ファイルが約32GB あり、 iTunes の無駄使いが過ぎる。デスクトップにも 50GB 近く使っていてその合計が 368GB にもなる。つまりC:ドライブの半分以上が本来の目的意外に食われている事になる。

iPhone/iPad のバックアップファイルは機種毎に作成されるようで、それらを買い替える度に巨大なファイルが残り、意識的に掃除しない限り残り C: ドライブを圧迫する。iPhoneの バックアップファイルはユーザデータ(写真と動画がメイン)をバックアップしているようだが、最近の大容量化した iPhone を使う上では盲点だろう。1TBの iPhone を買うならそのために PC に 1TB 近いストレージを用意する必要がある。こんなバックアップファイルは少なくともC: ドライブからは追い出す必要があると思う。C: ドライブはパフォーマンス向上のため SSD 化しているし、その大部分をほぼ使う可能性が無いバックアップに提供するのは勿体ない。

Copilot に相談したら、iTunes にはバックアップファイルの場所を変更するメニューは無いので(これは明らかに欠陥で、Apple に何とかして欲しい)リンクを使えとの事。ちなみにiTunes が作るバックアップファイルの格納場所には2種類あるようだが私の場合は次の場所だった(ちなみに MobileSync は隠しフォルダなのでフォルダーオプションを変更しないと表示されない)。

 C:\Users\ユーザー名\Apple\MobileSync\Backup\

そこでバックアップファイルを D: ドライブの中に作った次のフォルダへコピーし、

 D:\Apple\ios-backup\Backup\ 

そのフォルダのショートカットを作って元のフォルダに入れた(このとき元の Backup フォルダは消して(または別の名前にリネームして)使われないようにした)。これで上手く行くはずだ。

iTunesを立ち上げ「編集」->「環境設定」->「デバイス」を確認するとコピーしたバックアップファイルは認識されていない。iPhone を接続してバックアップを行うとC:ドライブにはBackupというショートカットに加えて消したはずの Backup という同名のフォルダが作られ、その中に新しいバックアップファイルが作られている。これを何度か繰り返して、埒が明かない。

なぜショートカットが認識されないんだ?ネットで成功事例を見ると、皆さんリンクはコマンドで作成していて、ショートカットを作成&コピーしてはいない。結果に違いはないように思うがこの方法を試しにやってみるか、と次のコマンドを管理者権限で cmd に与えた(ちなみに、この mklink は cmd の内部コマンドなので PowerShell ではエラーとなる)。

mklink /D "C:\Users\ユーザー名\Apple\MobileSync\Backup"  "D:\Apple\ios-backup\Backup"

iTunes を立ち上げると何故か過去のバックアップファイルをちゃんと認識してる。そしてバックアップも正しく D: ドライブの方へ格納される。理由は不明だがこれで上手く行ったようだ。

(終り)

追伸:Windows のショートカットとリンクの違いについては次の投稿「Windows のショートカットとリンクの違い」を参考に。

2026年4月3日金曜日

携帯内に混在する2つのPontaポイントの統合

 私の携帯(iPhone)内には10年以上前にLawsonで作ったカードをそのまま携帯アプリに移行したPontaポイントと、

AU Payアプリに紐づけられたPontaポイントが混在していた。

最初はこの違いに気づかなかったがポイントの数が違うことから2つが独立していることに気づいた。

Pontaのホームページ(PontaWeb)を見るとこれらを1つに纏める方法も書いてあるが、Pontaにも色々あるようで私がどれに該当するのか、或いは場合分けが多く、私の場合にどうすればよいのか分かりずらかった。じっさいLawsonで作ったPontaカードを携帯アプリに移行したものは今はRecruit IDに関連付けられていることを後に認識した。AU Payアプリに紐づけられたPontaは意識しないまま、いつの間にか登録されていた。

AU Pay ではPonta IDの切り替えも出来るようだが、これをやると統合は出来ず片方のPontaポイントを失う事になる。 

実際に手続きを進めると上手く行きそうなのに最後はエラーで駄目になる。手続きの途中でエラーで「失敗しました」と言われても何が悪かったのか原因も分からない。これまで時々挑戦しては失敗を繰り返してきたが、やっと成功したのでメモとして残す。

携帯アプリのPontaカードはRecruit IDに紐づけられていて、これをPonta-1とする。AU Pay に紐づけられたPontaカードをPonta-2とする。それぞれのカードは独立したPonta ID及びそれに対応して独立したPontaポイントを持っている。

〔Step-1〕 2つのカードの会員情報を合わせる(同じにする)。氏名や生年月日、電話番号等で、これが違っているとStep-2の手続きがエラーになる。私の場合、古いPonta-1は固定電話で登録していたので携帯に合わせた。
〔Step-2〕PontaWebにログインしてPonta-2のポイントをPonta-1に統合する。この手続きが済むとPonta-2は使えなくなる。
〔Step-3〕AU Pay でPonta-2との連携を解除する。Step-2の時点でAU Pay からPonta-2へはアクセスできなくなっているが、この解除を行わないとStep-4がエラーになる。
〔Step-4〕 AU Pay でRecruit IDを使ってPonta-1と連携させる。

これで携帯内の2つのPontaポイントは1つに統合された。 

メデタシ、メデタシ 

(上の記述の中に何点か誤りがあったので訂正しました) 

 

2026年3月28日土曜日

LiDARを使った距離計(その3)

 実際にソフトを組むにあたって最初に問題となったのは通信速度だ。当初データシートの記述を頼りに 460800bps と考えていたが、本当にそうなのか?Copilotは違う値(115200bps)を第一候補として答えてくるが・・・?

TSD-10に電源を入れると基板面で微小なLEDが点滅を繰り返していた。どうも電源ONと同時に連続測定をスタートしているようだ。こういったセンサーはスタートコマンドを送るまで測定を始めないかと思っていたが違ったようだ。それなら測定データをUARTで連続送信しているだろうから、その信号で通信速度を確認すればよい。

オシロスコープでTSD-10のUARTの波形を観測すると1ビットが約2μsに相当するようで、デフォルトの通信速度は 460800bps で間違いないようだ(計算上は1ビットが2.17μsになる)。

そこでPIC16F15325の内蔵UARTを使って近い速度の信号を作ってみたが上手く行かない。内蔵の16MHzや32MHzのクロックを分周して合わせてみても速度誤差が8.5%出てしまう。UARTは多少の誤差は許容できるはずだが、ここまで違うと無理のようだ。そこで内蔵UARTを使わず、ソフトで直接ポートを駆動してUART信号を作る事にした。送信するデータの1ビットずつを取出し、それに従ってポートをON/OFFする。この方法で作った実際の波形を観測すると、オーバーヘッドが大きく1ビットに最低3μsほどかかり2.17μs迄は下がらなかった。そこで最後の手段として、プログラム中にビット出力命令を直接並べて(ハンドエンコードして)実行する事とした。試しに1/0を交互に出力させて観測するとビット出力命令の実行時間は0.124μs程度(Fosc/4に相当)なので2μs程度の遅延を挟めば丁度良い信号を生成できる。そうやって出来上がったプログラムの1バイト送信分が次のようなものである。

     // 1 - 0x5A
    LATAbits.LATA4  = 0;   _delay3(5);  // Start bit 
        LATAbits.LATA4  = 0;   _delay3(5);  // b0        
        LATAbits.LATA4  = 1;   _delay3(5);  // b1      
        LATAbits.LATA4  = 0;   _delay3(5);  // b2      
        LATAbits.LATA4  = 1;   _delay3(5);  // b3      
        LATAbits.LATA4  = 1;   _delay3(5);  // b4      
        LATAbits.LATA4  = 0;   _delay3(5);  // b5      
        LATAbits.LATA4  = 1;   _delay3(5);  // b6      
        LATAbits.LATA4  = 0;   _delay3(5);  // b7      
    LATAbits.LATA4  = 1;   _delay3(10);  // Stop bit     
 

Stop bitを長めにとっているのは次のバイトのStart bitまでの時間に余裕を持たせ多少の同期ずれを回復できるようにするためである。 このプログラムは上手く機能した。これで測定の停止(Stop)とボーレートの変更は出来るようになった。ビットレートを一旦9600bpsまで落とせば後はPIC内蔵UARTでも余裕をもってコマンドを送信できる。

もう1つ問題になったのはTSD10からのデータが全く受信出来なかった事である。色々テストした結果判明した原因は、受信に使ったPICのA5ポートが全く機能しなかった事である。以前同様なトラブルが別のPICであり、解決策も以前書いたが今回はそれでも上手く行かなかった。色々試したが結局諦めて代わりに基板を改造してC5ポートを受信に使う事で解決した。 

TSD10の仕様によると測定データは毎秒50回(20ms毎に)送られてきて、精度は±5cm、ブラインドゾーン(無感距離)が5cmある。また分解能(測定結果)がmm単位なので使いづらい。そこで複数回(とりあえず10回)の測定の平均をとり、最下桁を丸めてcm単位を結果とするようにした。

実際に周囲の対象物の距離を測定してみるとブラインドゾーンはほとんど無く、測定値には10cm位のオフセットがあるがほぼ直線に乗っている(ただ30cmところだけ原因不明のずれがある)。

オフセットについてはTDS10の個体毎に異なると推察されるのでコマンドで設定できるようにした。またデータシートによると反射の無い状態(測定範囲外)では65535mmが送られてくるよう記載されているが、実際にTSD10を空に向けてみると最大探知距離である10000mmが送られてきてデータシートと異なる。

超音波センサーとLiDARを比べてみると、 超音波センサーは測定値に少しのふらつきが見られることもあるがLiDARは比較的安定しているようだ。これは統計処理の違いに起因するかも知れなく、これが問題になる場合は更なる検証が必要だろう。

TSD10は指向性が鋭いうえに赤外線は目に見えないので、どこの距離を測定しているか分かり辛い。レーザーポインタを併用すれば分かり易くなるが、逆にレーザーポインタが明るすぎて目に辛い。幸いレーザポインタの消費電流は多くないのでPICで直接駆動できる。レーザーポインタをソフト的にON/OFF出来るようにすればもっと使い易くなるだろう。

さて、これを何に使おうか? 

(終り)