2025年11月22日土曜日

KZ ZSN PRO 2を買ってみた。

 アリエクの11月11日セールでKZ ZSN PRO 2が安くなっていたので購入してみた。今までKZ ZNAを使っていたんだけども左右の音量に差が出てるような気がしたので…(他のイヤホンから付け替えたときに違和感があって気がついた)

KZ ZSN PRO 2は昔使っていたKZ ZSN PRO Xと同じ構成の進化系っぽいのでいいかなと。

箱が小さくなってる気がする。送料を抑えるための工夫かな?

スペックは裏側に書いてある。今回はマイク無しで1,390円だった。Amazonの半額以下とだいぶお安くなっている。
青いラインが入っているBlue-Blackにしてみた。ケーブルはZNAと同じ感じ。
イヤーピースはこんな感じ。
ケーブルに装着するときはいつも迷う。ミスると位相が逆になるので注意。

音質的にはZNAよりも女性ボーカルが出てくる感じで良い感じ。ZNAから切り替えると高音がすごい刺さってくる感じがあるけどこれはそのうち良くなるかな。

2025年11月9日日曜日

2208モータをセンサ付きFOC制御で回してみた。

 前回はSimpleFOCでOpenloopで回したんだけど、モータドライバ用アダプタを3Dプリントしてもらったのでセンサ付きFOCを試してみた。Openloopで回しただけなのにSimpleFOCで回したというタイトルはまずかったかな…

アダプタは今回もOnshapeで設計したのでフリープランなのでMars Power 2208とかで検索すれば出てくるかも。MT6701に付属のΦ4で2mm厚のマグネットをモータの軸に接着してMT6701と磁石の間が1.7mmぐらいになる感じにしておいた。

まずはアダプタをモータに装着して、その上から基板を装着する感じ。

モータにはあらかじめMT6701付属の磁石を貼り付けておいた。意外と真ん中に装着するのが難しい。アロンアルファでシャフトにダイレクトに貼り付け。

スケッチはこんな感じでとりあえずSimpleFOCで前回同様にキャリブレーションを行った。

#include "SPI.h"
#include "SimpleFOC.h"
#include "SimpleFOCDrivers.h"
#include "encoders/MT6701/MagneticSensorMT6701SSI.h"

#define MOTOR_POLE 7

#define CS1 PA15
#define SCK1 PB3
#define MISO1 PB4
#define MOSI1 PB5
#define DRV_EN PB12
#define TX3_SCL2 PB10
#define RX3_SDA2 PB11
#define CAN_RX PB8
#define CAN_TX PB9
#define V_CURR PA0
#define W_CURR PA1
#define U_CURR PA2
#define TEMP PA3
#define VOLTAGE PA4
#define LED_BUILTIN PC13


HardwareSerial Serial3(TX3_SCL2, RX3_SDA2);
SPIClass SPI_1(MOSI1, MISO1, SCK1);
MagneticSensorMT6701SSI sensor(CS1);
//InlineCurrentSense current_sense = InlineCurrentSense(0.01, -50 * 4, W_CURR, V_CURR, U_CURR);
InlineCurrentSense current_sense = InlineCurrentSense(0.01, -50 * 4, W_CURR, V_CURR, _NC);
BLDCMotor motor = BLDCMotor(MOTOR_POLE);
BLDCDriver6PWM driver = BLDCDriver6PWM(PA8, PB13, PA9, PB14, PA10, PB15, DRV_EN);  //W V U EN
float target_angle = 0;
Commander command = Commander(Serial3);


void toggleLED() {
  static bool state = false;
  state = !state;
  if (state) {
    GPIOC->BSRR = (1 << 13);
  } else {
    GPIOC->BSRR = (1 << (13 + 16));
  }
}

void doTarget(char* cmd) {
  command.scalar(&target_angle, cmd);
}

void doMotor(char* cmd) {
  command.motor(&motor, cmd);
}

void doAnalog(char* cmd) {
  if (cmd[0] == 'T') Serial3.println(analogRead(TEMP));
  else if (cmd[0] == 'V') {
    Serial3.print("Voltage: ");
    Serial3.println(analogRead(VOLTAGE) * 0.0032);
  }
};

void setup() {
  analogReadResolution(12);
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, HIGH);
  Serial3.setTx(TX3_SCL2);
  Serial3.setRx(RX3_SDA2);
  Serial3.begin(115200);
  SimpleFOCDebug::enable(&Serial3);

  sensor.init(&SPI_1);
  motor.linkSensor(&sensor);
  driver.voltage_power_supply = 8;
  driver.init();
  motor.linkDriver(&driver);
  current_sense.linkDriver(&driver);
  // control loop type and torque mode
  motor.torque_controller = TorqueControlType::foc_current;
  motor.controller = MotionControlType::velocity;
  motor.motion_downsample = 0.0;

  // velocity loop PID
  motor.PID_velocity.P = 1.5;
  motor.PID_velocity.I = 10;
  motor.PID_velocity.D = 0.0;
  motor.PID_velocity.output_ramp = 1000.0;
  motor.PID_velocity.limit = 2.0;
  // Low pass filtering time constant
  motor.LPF_velocity.Tf = 0.05;
  // angle loop PID
  motor.P_angle.P = 20.0;
  motor.P_angle.I = 0.0;
  motor.P_angle.D = 0.0;
  motor.P_angle.output_ramp = 0.0;
  motor.P_angle.limit = 40.0;
  // Low pass filtering time constant
  motor.LPF_angle.Tf = 0.0;
  // current q loop PID
  motor.PID_current_q.P = 1.5;
  motor.PID_current_q.I = 15.0;
  motor.PID_current_q.D = 0.0;
  motor.PID_current_q.output_ramp = 1000.0;
  motor.PID_current_q.limit = 5.0;
  // Low pass filtering time constant
  motor.LPF_current_q.Tf = 0.005;
  // current d loop PID
  motor.PID_current_d.P = 1.5;
  motor.PID_current_d.I = 15.0;
  motor.PID_current_d.D = 0.0;
  motor.PID_current_d.output_ramp = 1000.0;
  motor.PID_current_d.limit = 5.0;
  // Low pass filtering time constant
  motor.LPF_current_d.Tf = 0.02;
  // Limits
  motor.velocity_limit = 200.0;
  motor.voltage_limit = 5.0;
  motor.current_limit = 2.0;
  // sensor zero offset - home position
  motor.sensor_offset = 0.0;
  // general settings
  // motor phase resistance
  motor.phase_resistance = 1.06;
  // pwm modulation settings
  motor.foc_modulation = FOCModulationType::SpaceVectorPWM;
  motor.modulation_centered = 1.0;

  motor.useMonitoring(Serial3);

  motor.linkCurrentSense(&current_sense);
  current_sense.init();
  Serial3.print("current_sense.gain:\t");
  Serial3.print(current_sense.gain_a);
  Serial3.print(", ");
  Serial3.print(current_sense.gain_b);
  Serial3.print(", ");
  Serial3.println(current_sense.gain_c);
  Serial3.print("current_sense.offset_i:\t");
  Serial3.print(current_sense.offset_ia);
  Serial3.print(", ");
  Serial3.print(current_sense.offset_ib);
  Serial3.print(", ");
  Serial3.println(current_sense.offset_ic);

  motor.init();
  //motor.sensor_direction = Direction::CW;
  motor.initFOC();

  command.add('T', doTarget, "target angle");
  command.add('M', doMotor, "motor");
  command.add('A', doAnalog, "analog read");
  motor.monitor_downsample = 0;
  //motor.monitor_variables = 0;

  Serial3.println(F("Motor ready."));
  Serial3.println(F("Set the target angle using serial terminal:"));

  _delay(1000);
}

void loop() {
  motor.loopFOC();
  motor.move(target_angle);
  motor.monitor();
  command.run();
}

今回はインライン電流センサを2つに減らしてみたので使っていない層のピンアサインは"_NC"を指定している。ドキュメントだと2つのポートを指定しただけでも大丈夫そうに書いてあったんだけど初期化のときに"current_sense.offset_ia"が0になってしまって、bとcに値が入っていた。

とりあえず電流センサを2つに減らしても問題なく動いている。もともと2層だけのモータドライバも多いし。

SimpleFOC Studioでキャリブをしているけど9Vでも500mAぐらいしか流れないのでもっと電圧を高めたい感じ。しかしながらTMC6300が11Vまでという…
センサレスのときよりは断然トルクが上がっているのでギアボックスに組み込んでちゃんと回るかな。

2025年11月2日日曜日

2208モータをSimpleFOCで回してみた。

 知り合いが3Dプリントでジンバルモータ内蔵の流星ギアを作っていたみたいなんだけどトルクが全然無いので手でスタートしてやらないと回らないと…
ということでこの前作ったSimpleFOCモータドライバでセンサ付きで回してみることに。

モータはMars Powerの2208モータ。スペックとしては2208 80Tとしか書いてなくて電圧も2S-3Sとなってる。7.4V~11.1Vってことならこの前作ったモータドライバがちょうど良さそう。
ちなみに抵抗値は16Ω、114KVっぽい。ちょっと抵抗値が高いので電圧をかけないとトルクが出ないかも。

電流センサも2つで行けるだろうということで電流センサを2系統、CANトランシーバを装着しないやつをもう一枚作った。オペアンプをつけていないところのシャント抵抗の部分はジャンパした。

とりあえずモータにこの基板をつけるためのアダプタを設計しないといけないので磁気角度センサのMT6701はまだ実装していない。まずは動作確認してみる。

もともとモータのピンヘッダと合体できる設計なんだけど、今回はパッドからケーブルを出して接続。SimpleFOCでセンサなしでVelocity Open Loopで回してみた。
一応電流センサも動いてそう?ちゃんと回ってるけどこんな波形だっけ…
それにしてもOpenloopは発熱だけしてトルクがない感じがする。まだBack-EMFのフィードバックかけてるラジコン用ESCのほうがいいかも。
センサ付きFOC制御で回してみるのが楽しみ。

とりあえずモータとモータドライバのアダプタを設計して磁気アングルセンサ使えるようにしないと。

2025年10月22日水曜日

ESP32-CAMをWireGuard経由で使ってみた。

 ESP32でWireGuardが使えるライブラリがあるということで、ESP32-CAMもWireGuardを使ってNAT越えできるのではないかということで試してみた。

よくあるスマホのアプリ対応のWiFiカメラとかだとポート解放とか不要でユーザ登録すればカメラの映像を見れたりするけど、ESP32-CAMもモバイル回線とかのポート開放できないような場面で使えたら便利かも?ということで試してみた。前にUPnPでポートを開けてみたこともあったけど、そもそもグローバルIPがない環境だとこの方法も使えないし。

ESP32-CAM

久しぶりにESP32-CAMのスケッチをビルドするのでまずは環境を最新にしてビルドしてみた。ESP32 Arduino Coreは3.3.2でArduino IDEは2.3.5。前にESP32-CAMのESP32 Camera Driverを最新にしたときの手順で、ESP32 Camera Driver v2.1.3に更新してみた。
まずここで躓いたのがSCCB関連で、sccb-ng.cを削除したらうまくビルドできた。

最新の環境で前回のスケッチがビルドできるようになったので早速WireGuardを組み込んでみる。

とりあえずESP32のWireGuardライブラリをダウンロードしてきてサンプルをビルドしてみたんだけどまさかのArduino core for the ESP32 3.xに非対応という…

Pull requestに上がっていた3.x対応のESP32-WireGuardライブラリがあったのでこれをZipでダウンロードしてきて手動で追加した。テストでサンプルがビルドできたのでこのライブラリをESP32-CAMのスケッチに追加していく。

スケッチを全部貼ると長くなってしまうので前回のスケッチに追加した部分だけを紹介。

#define Tunnel

#ifdef Tunnel
#include <WireGuard-ESP32.h>
char private_key[] = "                                            ";  // [Interface] PrivateKey
IPAddress local_ip(10, 4, 0, 2);                                      // [Interface] Address
char public_key[] = "                                            ";   // [Peer] PublicKey
char endpoint_address[] = "     .f5.si";                      // [Peer] Endpoint
int endpoint_port = 51810;                                            // [Peer] Endpoint
static WireGuard wg;
#endif

まずは各種ライブラリをincludeしてるあたりにこんな感じで設定を追加。サーバ側はこの前設定したDD-WRTなんだけど、ESP32-CAM用にPeerを追加した。IPは同じセグメントで別なIPを割り振る感じ。Peerの設定はクライアントに割り振るIP以外ほぼコピーかな。ESP32ではQRコードではなくてQRコードの下にあるExport Peer Configボタンを押してコンフィグファイルをダウンロードする。そんでもってそのファイルの中のPrivateKeyってところとPublicKeyってところをコピーして上に貼り付け。DD-WRTの設定画面からじゃなくて、コンフィグファイルをダウンロードしてその中身ってのがポイント。

#ifdef Tunnel
  Serial.println("Adjusting system time...");
  configTime(9 * 60 * 60, 0, "ntp.jst.mfeed.ad.jp", "ntp.nict.jp", "time.google.com");

  struct tm timeInfo;
  if (getLocalTime(&timeInfo)) {
    Serial.print("Local Time  : ");
    Serial.printf("%04d-%02d-%02d ", timeInfo.tm_year + 1900, timeInfo.tm_mon + 1, timeInfo.tm_mday);
    Serial.printf("%02d:%02d:%02d\n", timeInfo.tm_hour, timeInfo.tm_min, timeInfo.tm_sec);
  }
#endif

WireGuardはNTPから時間を取得する必要があるのでNTPで時計を合わせる部分をsetup()の中のネットに繋がったあたりに追加。mDNSの上ぐらいに追加しておいた。

#ifdef Tunnel
  Serial.println("Initializing WG interface...");
  if (!wg.begin(
        local_ip,
        private_key,
        endpoint_address,
        public_key,
        endpoint_port)) {
    Serial.println("Failed to initialize WG interface.");
  }
#endif

そしてWireGuardに接続する部分をsetup()の中のserver.onの直前ぐらいに追加。これでWireGuardに接続されるとESP32の通信自体が全部WireGuardに流れる形。起動後10.4.0.2に接続すると普通にカメラの映像を確認できた。スタックも前回増やした状態でいい感じに安定して動いている。

ライブラリを使うことによって意外と簡単にESP32からWireGuard VPNに接続できちゃった。これならグローバルIPがなかったり、ポートが開けられない環境に設置して、サーバ側からポート変換で公開できちゃうので便利。お互いにNAT越えできない場合はTailscaleとか使えると便利そうだなぁ。

mDNSライブラリもそのまま動いてるみたいだけどWireGuardで接続している同セグメントからじゃないとmDNSでは解釈できないみたい。他のWireGuardのPeerからはうまく発見できた。IPだとWireGuard経由でも、ESP32-CAMがつながってるWiFiネットワークからでもどっちからもアクセスできた。

フレームレートの比較はどうやってやればよいのかな…

2025年10月19日日曜日

DD-WRTでWireGuardを使ってみた。

 DD-WRTでSoftEther VPNの設定がうまく行かなくなっていてからOpenVPNを設定して使っていたんだけども、WireGuardのほうが速いという噂を聞いたので試しにDD-WRTでWireGuardの設定をしてみて速度を比較してみた。

WireGuardの設定はOpenVPNとかSoftEtherVPNの設定画面とは別で、SetupのTunnelsから設定できる。DD-WRTのバージョンは62170以上が推奨みたい。

ここのAdd Tunnelを押すと各種Tunnelを追加できるようになっている。色々調べて設定してみたんだけど、どうしてもネットに繋がらなくなってしまうので最終的に公式フォーラムに登録してドキュメントを見ながら設定してみた。

Add Tunnelを押したら新規でTunnelが追加されるのでWireGuardを選択して設定していく。

Generate Keyボタンを押してLocal Public Keyを生成して、Advanced SettingsはEnableにして上のように設定する。MTUはインターネットのMTUからWireGuardのヘッダー分の60を引いた値にしておいた。サーバIPはドキュメント通りに10.4.0.1にしておいた。

とりあえず設定できたらSave & Applyで保存する。

次にクライアントの追加。

Add Peerボタンを押すと設定項目が追加されるのでPeerの設定をしていく。

Client Config FileをEnableにして、とりあえずクライアントのIPはドキュメント通りの10.4.0.6で設定。Peer Tunnel Endpointはサーバのアドレス設定ということで、外部からアクセスできるようにDDNSを設定した。DNSはとりあえずGoogle DNSを設定しておいた。
今回は1デバイス毎にPeerを設定しているのでAllowed IPsのサブネットマスクは/32にしておいた。/24とかにすると同一セグメントのPeer設定を増やすとうまく繋がらなかったり。

あとはMake Peer Configを押すとQRコードが生成される。
とりあえずこれでサーバ側の設定は完了。今回はiPhoneを接続したいのでiPhoneにWireGuardアプリをインストールしてQRコードをスキャンするだけでつながった。

OpenVPNの設定よりも簡単かも…

というわけで次に速度の測定をしてみた。

DD-WRT自体はOpenVPNもWireGuardもどっちもオンの状態で、同じ回線でiPhone側のVPNの切り替えだけでテスト。

まずはVPN不使用で回線のスピードをテスト

これがVPNでどこまで遅くなるのか…
OpenVPNはこんな感じ。何故かいつもダウンロードだけ遅いのよね。ルータの暗号化性能?
WireGuardはこんな感じ。結構速い。上りも下りも同じ速度が出ている
ちなみにルータの性能としてはBCM4708の800MHzデュアルコア、RAM512MBといったところ。
どっちも共存できてるので色んなところでつながるかテストしてみようかな。MTUも調整しないといけないかも。

ちなみにDD-WRTでやってるDNS広告ブロックもWireGuard経由で有効にできないかなといろいろ試してみたんだけど、PeerのDNS設定を10.4.0.1にしてDD-WRTのdnsmasq設定にWireGuardのセグメントを追加するだけ(listen-address=10.4.0.1)で行けた。そういえばOpenVPNの時もこんな設定にしていたな…