2025年10月12日日曜日

MediaMTXでラズパイカメラV1の映像をストリーミングしてみた。

 この前作ったダイレクトドライブなラジコンにカメラを搭載してFPVで操作してみたいと思って、LuckFox上でμStreamerを動かしたやつを載せてみたんだけど、このモータが結構早いのでもっと低遅延にしたい。

ということでもっと低遅延にできる方法が無いかどうか試してみることに。LuckFoxはビルドしてFlashに書き込んでっていうのが何回も往復するのが面倒なので、まずはRaspberry Pi Zero上で試してみることに。

久しぶりにRaspberry Pi Zeroを出してきたのでまずはOSをクリーンインストール。Raspberry Pi Imagerからセットアップして、WiFi設定までできるのは便利になったなぁ。Debian Trixieになった。

カメラ回りも少し仕様が変わっているみたい。raspi-configからの設定はいらなくて自動で認識するとか。

とりあえずセットアップが終わったRaspberry Pi Zeroに昔購入したRaspberry Pi Camera V1の互換品を取り付けて起動してみる。この前uStreamerで不安定だったリベンジ。

起動したら

rpicam-hello --list-cameras

と実行してみる。前のバージョンではlibcameraだった部分がrpicamに変更になっている感じかな?

Available cameras
-----------------
0 : ov5647 [2592x1944 10-bit GBRG] (/base/soc/i2c0mux/i2c@1/ov5647@36)
    Modes: 'SGBRG10_CSI2P' : 640x480 [58.92 fps - (16, 0)/2560x1920 crop]
                             1296x972 [46.34 fps - (0, 0)/2592x1944 crop]
                             1920x1080 [32.81 fps - (348, 434)/1928x1080 crop]
                             2592x1944 [15.63 fps - (0, 0)/2592x1944 crop]

こんな感じで差し込んだだけで認識してるみたい。互換品でもとりあえず問題なく自動で認識できた。確かに電源を入れたときに一瞬カメラのLEDが点灯したのでその時に確認してるのかな?

rpicam-jpeg -n --output test.jpg

次に静止画を撮影するコマンドで写真を撮影して、WinSCPでPCに転送して確認してみた。(SSHで作業しているため)
写真もバッチリ取れてるのでまずはカメラの動作確認はOK。

カメラの動作確認ができたのでMediaMTXをダウンロードして実行可能にする。MediaMTXはWebRTCを使ってカメラの映像をブラウザからストリーミングできる。

wget -P ./mediamtx https://github.com/bluenviron/mediamtx/releases/download/v1.15.1/mediamtx_v1.15.1_linux_armv6.tar.gz
cd mediamtx
tar xzf mediamtx_v1.15.1_linux_armv6.tar.gz
chmod +x mediamtx

ラズパイZeroWにはH264のハードウェアエンコーダが内蔵されてるのでせっかくなので使ってみようということでH264でWebRTCでストリーミングのテストをしてみる。同じフォルダのmediamtx.ymlを開いて一番下の方のpaths:っていうサンプルがあるところをまるっと下記のように置き換える。

paths:
  cam0:
    source: rpiCamera
    sourceProtocol: automatic
    rpiCameraWidth: 1296
    rpiCameraHeight: 972
    rpiCameraFPS: 30
    rpiCameraBitrate: 2000000
    rpiCameraCodec: hardwareH264

保存したらあとは./mediamtxでMediaMTXを起動するだけ。

ブラウザからhttp://raspberrypi.local:8889/cam0/にアクセスしてみるとカメラの映像が見れるようになってるはず。MediaMTX簡単で良いな…

早速遅延を図ってみた。前に使ったように画面にストップウォッチを表示して、その隣にストリーミングの映像を表示させてスクショを撮るという方法。

遅延は440msぐらい?ラズパイZero WはWiFiでルータに接続してる状態で測定。ハードウェアエンコーダとはいえH264に変換してるからかなぁ?
カメラの方が赤みがかっているのはレンズ改造のところに隙間ができていたからかな。隙間埋め無いと…

UVC対応のWebカメラでuStreamerを同じラズパイZero Wで試してみた。

とりあえず前と同じ感じで最新版をビルドした。

ustreamer -m MJPEG -f 30 -s 0.0.0.0 -p 8080 -r 1280x720

解像度はLogicool C270の解像度に合わせて近いところに合わせてみた。

そんでもって遅延を測定すると140msぐらい。ラズパイ上だと結構低遅延じゃん…
しかしこの解像度でもMJPEGだと帯域は8.5Mbpsぐらいあったので、H264の2Mbpsだと電波状況が悪いときは有利かも。

H264ハードウェアエンコード対応のWebカメラも試してみたいな。UVCのH264も実装が色々あるみたいでLinuxで使う場合はUVC1.5対応じゃないとちょっと大変そう。DJI Osmo Action 4とかもUSBカメラモードでH264に対応してるっぽいけど、UVC1.0なのでV4L2からそのまま取り出せなさそう

2025年10月5日日曜日

Windows 11へのアップグレードが31%で止まってしまった。

 そろそろWindows 10のサポートが終了するということで友人がWindows 11にアップグレードしようとしたところどうしてもアップグレードできないということで調べてみた。Windows Updateとかでも止まるらしいので最終手段のisoからインストールも試したみたいなんだけどどうしても毎回31%で止まってしまうらしい。
Windows 8のライセンスを使ってアップグレードでWindows 10をクリーンインストールしているんだけども、2023年9月27日からWindows 8のライセンスも使えなくなってしまったので再インストールしたらWindows 11のライセンスを購入しないといけない可能性もあるし…(ハードウェア変更無いので行けると思うけど。)
そもそも再インストール自体が面倒。

巷で噂のConexant SmartAudio HDデバイスは存在していないようだけれども…

ということでひとまずログファイルを確認してみることに。

C:\Windows\Panther

の中に「setupact.log」と「setuperr.log」というファイルが生成されていて、これがアップグレード時に残るログファイルらしい。

ログファイルは結構大きいけど、おそらく一番下の方だけを確認すれば良さそうな気がする。「setupact.log」の方を確認すると一番下の方でMIGのInfoがあったあとにMOUPGのErrorが出てログが途絶えていた。

2025-10-05 10:10:20, Info                  MIG        AddDriverFiles: Processing device: 4d36e96c-e325-11ce-bfc1-08002be10318
2025-10-05 10:10:20, Info                  MIG        AddDriverFiles: Processing driver: Sennheiser Communication Audio, Sennheiser, Sennheiser
2025-10-05 10:10:20, Info                  MIG        AddInfAndCatalog: Adding catalog file: C:\WINDOWS\system32\catroot\{f750e6c3-38ee-11d1-85e5-00c04fc295ee}\oem40.cat
2025-10-05 10:13:35, Info                  MOUPG  CInstallUI::ShowMessageBox: Showing MessageBox
2025-10-05 10:13:37, Info                  MOUPG  CInstallUI::ConfirmCanceled: User cancel confirmed
2025-10-05 10:13:37, Info                  MOUPG  CInstallUI::OnProgressChanged: Cancel is requested. Returning ERROR_REQUEST_ABORTED
2025-10-05 10:13:37, Error                 MOUPG  CInstallUI::OnProgressChanged(579): Result = 0x800704D3
2025-10-05 10:13:37, Error                 MOUPG  CSetupUIManager::OnProgressChanged(452): Result = 0x800704D3
2025-10-05 10:13:37, Error                 MOUPG  CSetupHost::OnProgressChanged(2531): Result = 0x800704D3
2025-10-05 10:13:37, Error                 MOUPG  CSetupManager::DlpManagerCallback(2341): Result = 0x800704D3
2025-10-05 10:13:37, Info                  MOUPG  Cancel of current task requested...
2025-10-05 10:13:37, Info                  MOUPG  Attempting to cancel current task...
2025-10-05 10:13:37, Info                  MOUPG  MoSetupPlatform: Calling SetupPlatform::INewSystem::RequestCancelOperations...
2025-10-05 10:13:37, Info                  MOUPG  Task cancel request returned: [0x0]
2025-10-05 10:13:37, Error                 MOUPG  SendCallbackMessage: [0x7] -> user callback returned 0x800704D3
2025-10-05 10:13:37, Error                 MOUPG  CDlpTask::Cancel(993): Result = 0xC1800108
2025-10-05 10:13:37, Info                  MOUPG  SendCallbackMessage: [0x7] -> Cancel request returned 0xC1800108

こんな感じのエラー。

この感じだと実はゼンハイザーのUSBヘッドホンが引っかかってる?

何度もアップデートが失敗しているのでUSBデバイスは抜いているんだけども、抜いていてもインストールしているドライバを移行する際にエラーで止まってしまってる模様。

ということでもう一度ゼンハイザーのUSBヘッドホンを差し込んでデバイスマネージャーからドライバを丸ごと削除してみる。

デバイスマネージャーのゼンハイザーのデバイスを右クリックしてデバイスのアンインストールを選択。

そんでもって出てきたダイアログで「このデバイスのドライバーソフトウェアを削除します。」にチェックを入れてアンインストール。とりあえずSennheiserのデバイス2つともこんな感じで削除。
削除したあとにUSBデバイスのスキャンが入ってしまうとまたインストールされてしまうので削除が終わったらUSBデバイスを抜く。

そんでもってWindows 11アップデートを掛けたら…

魔の31%を超えることができた。やっぱりWindowsのアップグレードはオーディオデバイスがネックになっているのかもしれないね。

EPOSのサイトにはドライバアンインストールツールでのアンインストール方法も公開されてるのでこれも参考にするとドライバをちゃんとアンインストールできるかもしれない。こっちのほうがデバイスマネージャーに表示されない部分も表示されるかもしれないので便利かも?

とりあえずWindows 11のアップグレードが止まってしまったらログファイルをみればなんとかなるかもしれないことがわかった。SetupDiagという、ログを見やすくする便利なツールもあるらしい。

ちなみに今回引っかかったゼンハイザーのGSP 350はアナログケーブルも市販(4極から3極も変換できるし)されてるようなので、こうやってドライバアップデートに引っかかるようだったら普通にアナログ化してオンボのRealtekとかで動かしても良さそう。7.1chのWindows 11対応のドングルに変更しても良いし。

2025年9月28日日曜日

タミヤのボールキャスターを試してみた。

 この前作った対向2輪型のラジコンなんだけど、高速で直進すると左右にゆらゆらするのでキャスターの制度が悪いからキャスターが振られてるんじゃないか?ということでボールキャスターに変更してみた。

金属製なので床が傷つくんじゃないかということで長らく放置していたタミヤのボールキャスター。
組み立ては超簡単。
キャスター用の高さ調整プレートはネジ穴を貫通にしてあるのでボールキャスターの高さ調整にも使用可能。ボールキャスターも35mmで組み立てたのでピッタリ。

実際に走らせてみるとやっぱりゆらゆらする。ということはSimpleFOCのPIDチューニングかもしれない。速度フィードバックで速度が一定になっていないのかなぁ。これはさらなるチューニングが必要かもしれない。
この前CAN経由でチューニングできるようにしたのでESP32側にちょっと変更できるようなしくみを入れようかな。

それはそうと、ボールキャスターになったことでフロントが軽くなってフロントが浮くようになってしまった。

重さはキャスターが36gでボールキャスターが12gだった。これはフロントが軽くなる。
キャスター変えただけでこんなに機敏に動くようになるなんて…
これジャイロつけたらずっとウィリーとかできそうな?

2025年9月27日土曜日

ESP32にLiDARを繋いでブラウザに表示してみた。

 前に購入した格安LiDARをESP32に繋いでブラウザでデータを表示するようにしてみた。購入したときにESP32に接続してデータ取得するところまではやってみたんだけど、今回はその取得できたデータをWebSocketでブラウザに送って可視化してみることに。

今回はM5AtomS3が余っていたのでこれに繋いでみることに。Groveコネクタは信号線が3.3Vだけど電源は5V取れるので好都合。GroveコネクタにLiDARが直結できるなんて便利かも。

色々試してみたんだけど、JSONでデータを送るとデータ量が多すぎてうまく360°分のデータが送れなかったので距離データだけをバイナリでWebSocketで送るようにしてみた。

今更気がついたんだけど、LDS-006って回転方向がCCWなのね…普通LiDARを手に持って回転させると画面の可視化データって反対回りに動くはずなんだけど同じ向きに回る。ということでそこもデータを取得するところで修正した。

#include <Arduino.h>
#include <WiFi.h>
#include <WebSocketsServer.h>
#include <WebServer.h>
#include <ESPmDNS.h>

#define RXD2 2
#define TXD2 1

// WiFi設定
const char* ssid = "SSID";
const char* password = "password";

WebServer server(80);  // Webサーバー port80
WebSocketsServer webSocket = WebSocketsServer(81);// WebSocketサーバー port81

// LDS-006 360点分の距離データ
volatile uint16_t lidar360[360];
volatile bool newData = false;

// LDS-006読み取りタスク
void taskLidarRead(void* pvParameters) {
  Serial2.begin(115200, SERIAL_8N1, RXD2, TXD2);
  delay(100);
  Serial2.print("startlds$");  // 起動コマンド

  uint8_t packet[22];
  int packetIndex = 0;

  while (1) {
    while (Serial2.available()) {
      uint8_t b = Serial2.read();
      if (packetIndex == 0 && b != 0xFA) continue;
      packet[packetIndex++] = b;
      if (packetIndex == 22) {
        if (packet[0] == 0xFA) {
          uint8_t block = packet[1];
          if (block >= 0xA0 && block <= 0xF9) {
            int baseAngle = (block - 0xA0) * 4;
            for (int i = 0; i < 4; i++) {
              int offset = 4 + i * 4;
              uint16_t dist = ((packet[offset + 1] & 0x3F) << 8) | packet[offset];
              int angle = baseAngle + i;
              if (angle >= 360) angle -= 360;
              int correctedAngle = (360 - angle) % 360;
              lidar360[correctedAngle] = dist;
            }
            newData = true;
          }
        }
        packetIndex = 0;
      }
    }
    vTaskDelay(1);
  }
}

// WebSocket送信タスク
void taskWebSocketSend(void* pvParameters) {
  while (1) {
    if (newData) {
      // 360点分の距離データを2バイトずつのバイナリで送信
      webSocket.broadcastBIN((uint8_t*)lidar360, 360 * 2);
      newData = false;
    }
    vTaskDelay(100);  // 100ms周期
  }
}

void taskHttpServer(void* pvParameters) {
  while (1) {
    server.handleClient();
    vTaskDelay(1);
  }
}

// WebSocketイベント
void webSocketEvent(uint8_t num, WStype_t type, uint8_t* payload, size_t length) {
  if (type == WStype_CONNECTED) {
    Serial.printf("WebSocket client #%u connected\n", num);
  }
}

#define index_html_gz_len 517
const uint8_t index_html_gz[] PROGMEM = {
  0x1F, 0x8B, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x55, 0x53, 0x5D, 0x6B, 0xDB, 0x30,
  0x14, 0x7D, 0x1F, 0xEC, 0x3F, 0xDC, 0x99, 0x41, 0xEC, 0x36, 0xB3, 0x9C, 0x8C, 0x85, 0x52, 0xDB,
  0x81, 0x2D, 0xED, 0x43, 0xA1, 0x65, 0x65, 0xE9, 0x18, 0x63, 0x0C, 0xAA, 0xD8, 0x4A, 0x2C, 0xA6,
  0x48, 0x45, 0xBA, 0x89, 0x63, 0x4A, 0x5E, 0xF6, 0x30, 0xF6, 0x97, 0xF6, 0x83, 0xC6, 0xFE, 0xC6,
  0x24, 0x7F, 0x24, 0xCE, 0x8B, 0xA5, 0xAB, 0x7B, 0xCE, 0xD5, 0x3D, 0x47, 0xBE, 0xC9, 0xAB, 0xAB,
  0x8F, 0xB3, 0x87, 0xAF, 0xF7, 0xD7, 0x50, 0xE0, 0x5A, 0x4C, 0x5F, 0xBE, 0x48, 0x0E, 0x2B, 0xA3,
  0xB9, 0x5D, 0x01, 0x12, 0xE4, 0x28, 0xD8, 0xF4, 0xF6, 0x6A, 0xFE, 0x26, 0x8A, 0x26, 0x70, 0xCB,
  0xB7, 0x2C, 0x21, 0xCD, 0x99, 0x85, 0x91, 0x16, 0x97, 0x2C, 0x54, 0x5E, 0xB9, 0x35, 0xA3, 0x72,
  0x4B, 0x0D, 0xF0, 0x3C, 0xF5, 0x04, 0xCF, 0xA9, 0x9E, 0xD5, 0xB1, 0x07, 0x25, 0xCF, 0xB1, 0x48,
  0xBD, 0x77, 0x51, 0xE4, 0x41, 0xC1, 0xF8, 0xAA, 0xC0, 0x26, 0x98, 0x26, 0xA4, 0xA1, 0x38, 0xB2,
  0xC9, 0x34, 0x7F, 0x42, 0xBB, 0xCB, 0x94, 0x34, 0x08, 0x6D, 0xAD, 0x14, 0x72, 0x95, 0x6D, 0xD6,
  0x4C, 0x62, 0xB8, 0x62, 0x78, 0x2D, 0x98, 0xDB, 0x7E, 0xA8, 0x6E, 0x72, 0x7F, 0xD0, 0xBB, 0x62,
  0x10, 0xC4, 0x07, 0x1E, 0xEE, 0x2C, 0xA9, 0x61, 0x3B, 0xCA, 0x4C, 0x49, 0x64, 0x3B, 0xF4, 0x07,
  0xE3, 0xBC, 0x87, 0x2A, 0x94, 0xFD, 0xA4, 0xB6, 0x31, 0x99, 0xAB, 0x32, 0x14, 0x2A, 0xA3, 0xC8,
  0x95, 0x0C, 0xDD, 0xB1, 0xA4, 0x6B, 0x76, 0xC0, 0x95, 0xAE, 0x03, 0xC9, 0x4A, 0xF8, 0xC2, 0x16,
  0x73, 0x95, 0xFD, 0x60, 0xE8, 0x3F, 0x96, 0xE6, 0x92, 0x90, 0xD7, 0xCF, 0x0E, 0xBB, 0xBF, 0xBC,
  0x18, 0x3D, 0xBA, 0xAA, 0xA5, 0x09, 0x17, 0x5C, 0x52, 0x5D, 0x3D, 0x54, 0x4F, 0xCC, 0x52, 0x3C,
  0xAA, 0x35, 0xAD, 0x16, 0x9B, 0xE5, 0x92, 0x69, 0xCF, 0xE6, 0x6B, 0x84, 0x92, 0x6B, 0x66, 0x0C,
  0x5D, 0x39, 0x80, 0xCF, 0xB6, 0x56, 0x48, 0x00, 0xE9, 0x14, 0x9E, 0x9D, 0xD3, 0xCD, 0x75, 0x0D,
  0xC1, 0xA6, 0xEB, 0x6C, 0x98, 0x53, 0xA4, 0xF1, 0x31, 0x9B, 0x73, 0x83, 0xEF, 0x5D, 0xDD, 0xB6,
  0xA7, 0xCF, 0x5C, 0xE2, 0x68, 0x52, 0x9F, 0xF8, 0x0D, 0x33, 0x88, 0x81, 0x10, 0x78, 0x3B, 0x89,
  0xFE, 0xFD, 0xFC, 0xF3, 0xF7, 0xF7, 0xAF, 0x9A, 0x8A, 0xBB, 0x30, 0x13, 0x8C, 0xEA, 0x4F, 0x2C,
  0x43, 0x3F, 0x1A, 0x46, 0xC3, 0xD6, 0x9B, 0xFA, 0x51, 0xBA, 0xA0, 0x79, 0x95, 0x20, 0xEE, 0x18,
  0x86, 0x6E, 0x99, 0x7F, 0x0C, 0x51, 0x53, 0x69, 0x04, 0x45, 0xE6, 0xF7, 0xC9, 0x64, 0x3C, 0x84,
  0x13, 0x3E, 0x19, 0x37, 0x94, 0xA5, 0xD2, 0xBE, 0x60, 0x08, 0x3C, 0x8D, 0x62, 0x9E, 0xD8, 0x76,
  0x62, 0x7E, 0x7E, 0x1E, 0xD4, 0x3A, 0x3B, 0x2D, 0x54, 0xAE, 0x84, 0xF3, 0x81, 0xC3, 0x19, 0xDC,
  0x51, 0x2C, 0xC2, 0xFB, 0x1B, 0x20, 0x30, 0xBA, 0x88, 0xE2, 0x3E, 0xCA, 0x59, 0x71, 0x50, 0xFD,
  0x8D, 0x7F, 0x3F, 0x8B, 0xC2, 0x51, 0x0C, 0x7D, 0x84, 0x7B, 0x6C, 0xDD, 0xD5, 0xC8, 0x94, 0xF1,
  0xEB, 0xC2, 0xC1, 0x49, 0x95, 0xAA, 0x8F, 0x31, 0x5C, 0x1E, 0x31, 0x2D, 0xCA, 0x2A, 0x5C, 0x72,
  0x21, 0xE6, 0x58, 0xD5, 0x3D, 0x79, 0x0B, 0xB1, 0x61, 0x5E, 0x7C, 0x9A, 0xAC, 0xED, 0xDB, 0x0D,
  0xA1, 0x1A, 0x82, 0x95, 0xDD, 0x0A, 0xDD, 0x77, 0x06, 0x69, 0x66, 0x50, 0xE9, 0xC6, 0xB2, 0x7D,
  0xEC, 0x66, 0xE3, 0xF0, 0x3B, 0x27, 0xA4, 0x1B, 0x0F, 0x52, 0xCF, 0xD7, 0x7F, 0x40, 0x2D, 0x47,
  0x49, 0x77, 0x03, 0x00, 0x00
};

void setup() {
  Serial.begin(115200);

  // WiFi接続
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("WiFi connected. IP: ");
  Serial.println(WiFi.localIP());
  MDNS.begin("esp32");

  // WebSocket
  webSocket.begin();
  webSocket.onEvent(webSocketEvent);

  // タスク作成
  xTaskCreatePinnedToCore(taskLidarRead, "LidarRead", 4096, NULL, 2, NULL, 1);
  xTaskCreatePinnedToCore(taskWebSocketSend, "WebSocketSend", 4096, NULL, 1, NULL, 1);
  xTaskCreatePinnedToCore(taskHttpServer, "HttpServer", 4096, NULL, 1, NULL, 1);

  server.enableCORS();
  server.on("/", HTTP_GET, []() {
    server.sendHeader(F("Content-Encoding"), F("gzip"));
    server.send_P(200, "text/html", (const char*)index_html_gz, index_html_gz_len);
  });
  server.begin();
}

void loop() {
  webSocket.loop();  // WebSocketのループ
}

これでESP32を起動するとLiDARが回り始めて、http://esp.localにアクセスするとLiDARのデータがプロットされる。Webページのデータもスケッチにgzで圧縮して埋め込んでしまったんだけど、やっていることはWebSocketでデータを受け取ったやつをXY座標を三角関数で計算してCanvasでプロットしてるだけ。

こんな感じでiPhoneからアクセスしてもリアルタイムでデータが送られてくる。Androidの場合はmDNS非対応なのでIPアドレスでアクセスする必要があるかも。

中心線が無いと動きがわかりにくいので中心線とかグリッドとか入れておいたほうがいいかな?

2025年9月23日火曜日

モータドライバに通信タイムアウトを入れてみた。

 OdriveライクなCAN通信でSimpleFOCのモータドライバと通信しているんだけど、電圧低下などでESP32がフリーズしたりするとモータが前回値でそのまま動いてしまうので通信切断検知を入れてみた。

この前のCAN対応ファームウェアに追加で通信がない状態が1000ms続いたらモータをフリーにするようにしてみた。坂道だとフリーなので走ってしまうかもしれないけど…

アクロバティックな走行をしていると途中でバッテリの電圧降下で回り続けるようになってしまったり。とりあえず通信切断検知を入れたらこの状態で止まってくれた。

とりあえず簡単に実装してみたやつでも動いてそう。

#define WATCHDOG 1000
unsigned long lastUpdate = 0;
boolean watchDogEnable = true;

void inputWatchdog_reset() {
#ifdef WATCHDOG
  lastUpdate = millis();
  motor.enable();
#endif
}

とりあえず追加した変数と関数はこんな感じで通信の場所でinputWatchdog_reset()を読み続ければ通信切断は検知しない感じに。

#ifdef WATCHDOG
  unsigned long watchDogMs = millis();
  if (watchDogMs - lastUpdate > WATCHDOG && watchDogEnable == true) {
    motor.disable();
  }
#endif

切断検知はloop()関数内にこんな感じで追加。

        case 0x00C:  //Set Input Pos
          if (CAN_RX_msg.type == DATA_FRAME) {
            FLOAT_BYTE_UNION input;
            memcpy(input.uint, CAN_RX_msg.data, 8);
            motor.controller = MotionControlType::angle;
            target_angle = input.value[0];
          }
          inputWatchdog_reset();
          break;
        case 0x00D:  //Set Input Vel
          if (CAN_RX_msg.type == DATA_FRAME) {
            FLOAT_BYTE_UNION input;
            memcpy(input.uint, CAN_RX_msg.data, 8);
            motor.controller = MotionControlType::velocity;
            target_angle = input.value[0];
          }
          inputWatchdog_reset();
          break;
        case 0x00E:  //Set Input Torque
          if (CAN_RX_msg.type == DATA_FRAME) {
            FLOAT_BYTE_UNION input;
            memcpy(input.uint, CAN_RX_msg.data, 8);
            motor.controller = MotionControlType::torque;
            target_angle = input.value[0];
          }
          inputWatchdog_reset();
          break;

あとは前回同様CAN通信のswitch case分の中のモータを動かしそうな関数にinputWatchdog_reset()を追加していけばOK。これを追加するとSimpleFOC StudioからいじれなくなるのでそのときはwatchDogEnableをfalseにする関数をコマンドに追加してやろうかなと言う感じ。

使用してるジンバルモータが220kvなので1Sだと90rad/sぐらい回せそうなんだけども48rad/s制限で回してみると結構早い。頑張ればウィリーできるぞ…

電源もブレッドボードからとりあえず取ってるのでウィリーしてリアのケーブルを動かすと接触不良で通信が止まってしまうんだけど、ちゃんとモータが止まるようになった。
ウィリーした瞬間に通信が止まってモータが回り続けているけど1秒ぐらいで停止している感じ。
モータドライバのLEDが付いてるのでモータドライバは動いてそう。通信してるとブレーキが掛かるんだけど、通信していないとフリーになるので通信切断検知が動いてるのかわかりやすい。

結構モータが強いのでキャスターをどうにかしてもうちょっと走行性能を上げたいな。