AUTD3について

AUTD3は空中触覚用超音波フェーズドアレイデバイスである. 超音波フェーズドアレイとは, 位相を個別に制御できる超音波振動子を (典型的には格子状に) 配列したものである. 超音波の位相を制御することで, 空間に任意の音場を発生させることができる.

フェーズドアレイを用いて十分に集束させた音波のエネルギーは, 音響放射圧を生じる. この圧力を利用して, 人体の表面を非接触で押すことができる. 集束焦点の位置は, フェーズドアレイを電子的に制御することで自由に制御できる. また, 逆問題を解くことで, 単一の焦点だけでなく, より複雑な音圧空間分布を作ることもできる.

フェーズドアレイで生成できる圧力の大きさの上限は, 現在のところ約である. また, 空間分解能は使用する波長程度までとなる (例えば, で約). フェーズドアレイにはこのような制約はあるものの, 力の時空間分布を自由にデザインし, さまざまな触覚を作り出すことができる技術として注目されている.

このように非接触で触覚を刺激する技術分野を空中触覚 (Midair Haptics) と呼び, 我々はこの超音波空中触覚装置をAirborne Ultrasound Tactile Display (AUTD) と呼んでいる. AUTDの本質的な部分は, 2008年1から2010年代初頭2にかけて, 東京大学によって提案・確立された. その後, 各国の大学や企業が参入し, 活発な研究開発が行われている. AUTD3は我々東京大学篠田牧野研究室で開発しているAUTDの3代目のバージョンである.

研究室のホームページにAUTDを使った研究の一覧が掲載されている. こちらも参照されたい.

本マニュアルはこのAUTD3を操作するためのautd3ソフトウェアライブラリについてまとめたものである.

はじめに

ここではまず, AUTD3のハードウェアやファームウェア, ソフトウェアのセットアップについて述べる.

ハードウェア

AUTD3デバイス

AUTD3デバイスは1台あたり249個の振動子から構成されている1. さらに, 復数のデバイスをデイジーチェインで接続し拡張できるようになっている. SDKからはこれら全ての振動子の位相/振幅をそれぞれ個別に指定できるようになっている.

AUTDの表面写真
AUTD背面写真

AUTD3の座標系は右手座標系を採用しており, 0番目の振動子の中心が原点になる. x軸は長軸方向, すなわち, 0→17の方向であり, y軸は0→18の方向である.

また, 単位系として, 距離はmmを採用している. 振動子はの間隔で配置されており, 基板を含めたサイズはとなっている.

セットアップ

PCと1台目のEherCAT In をイーサネットケーブルを繋ぎ, 台目のEherCAT Outと台目のEherCAT Inを繋ぐ. この時, イーサネットケーブルはCAT 5e以上のものを使用すること.

AUTD3の電源はの直流電源を使用する. 電源については相互に接続でき, 電源コネクタは3つの内で好きなところを使って良い. なお, AUTD3デバイス側の電源のコネクタはMolex社5566-02Aを使用している.

NOTE: AUTD3はデバイスあたり最大での電流を消費する. 電源の最大出力電流に注意されたい.

寸法図

AUTD3デバイスの寸法
1

からネジ用に3つの振動子が抜けている. 態々この位置にネジ穴を持ってきたのは, 複数台並べたときの隙間を可能な限り小さくしようとしたため.

ファームウェア

ファームウェアのアップデートにはVivado, 及び, J-Link SoftwareをインストールしたWindows 10/11 64bit PCが必要である. なお, Vivado 2024.1, 及び, J-Link Software v8.10での動作を確認している.

NOTE: ファームウェアのアップデートだけが目的であれば, “Vivado Lab Edition“の使用を強く推奨する. ML Edition はインストールに60 GB以上のディスク容量を要求する. Lab Edition は6 GB程度のディスク容量で済む.

NOTE: 古いJ-Linkデバイスを使用する場合, “Install legacy USB Driver for J-Link (requires admin rights)“にチェックを入れること. 例えば, J-Link Plusの場合, V10以前はlegacy USB Driverが必要になる. (バージョンはJ-Link Plusデバイスの背面に書かれている.) 詳しくはSegger Wikiを参照されたい. 使用しているデバイスにWinUSB featureがあれば, legacy USB Driverは不要.

まず, AUTD3デバイスとPCをXILINX Platform Cable, 及び, J-Link 9-Pin Cortex-M Adapter付きのJ-Link Plusで接続し, AUTD3の電源を入れる. 次に, autd3-firmware内のautd_firmware_writer.ps1をpowershellから実行し, 指示に従えばよい. updateには数分の時間を要する.

git clone https://github.com/shinolab/autd3-firmware
cd autd3-firmware
pwsh autd_firmware_writer.ps1
ファームウェアアップデート用ケーブル接続先

ソフトウェア

基本的に各言語の標準的なパッケージマネージャーに対応している.

チュートリアル

ここでは, 実際にAUTD3を動かす手順について述べる.

単一デバイスの駆動

ここでは, 一つのデバイスを駆動する方法について説明する.

依存プログラムのインストール

本チュートリアルではSOEMを利用する. Windowsを使用する場合, Npcapを「WinPcap API-compatible Mode」でインストールしておくこと.

なお, ファームウェアが古い場合, 正常な動作は保証されない. 本文章におけるファームウェアのバージョンはv11.0.0, または, v10.0.11が想定される. ファームウェアのアップデートははじめに/ファームウェアを参照されたい.

サンプルコード

まずは適当なプロジェクトを作成し, autd3ライブラリを依存関係に追加する. また, デバイスとの通信を行うautd3-link-soemライブラリも依存関係に追加する.

cargo new --bin autd3-sample
cd autd3-sample
cargo add autd3
cargo add autd3-link-soem

次に, src/main.rsファイルを編集し, 以下のようにする. これは単一焦点にのAM変調をかける場合のソースコードである.

use autd3::prelude::*;
use autd3_link_soem::{SOEMOption, Status, SOEM};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Open controller with SOEM link
    // Here, the AUTD3 device is placed at the origin
    let mut autd = Controller::open(
        [AUTD3 {
            pos: Point3::origin(),
            rot: UnitQuaternion::identity(),
        }],
        SOEM::new(
            // The first argument is a callback that is called when error occurs
            |slave, status| {
                eprintln!("slave[{}]: {}", slave, status);
                if status == Status::Lost {
                    // You can also wait for the link to recover, without exitting the process
                    std::process::exit(-1);
                }
            },
            // The second argument is a option of SOEM link.
            SOEMOption::default(),
        ),
    )?;

    // Check firmware version
    // This code assumes that the version is v11.0.0 or v10.0.1
    autd.firmware_version()?.iter().for_each(|firm_info| {
        println!("{}", firm_info);
    });

    // Enable silencer
    // Note that this is enabled by default, so it is not actually necessary
    // To disable, send Silencer::disable()
    autd.send(Silencer::default())?;

    // A focus at 150mm directly above the center of the device
    let g = Focus {
        pos: autd.center() + Vector3::new(0., 0., 150.0 * mm),
        option: FocusOption::default(),
    };

    // 150 Hz sine wave modulation
    let m = Sine {
        freq: 150 * Hz,
        option: SineOption::default(),
    };

    // Send data
    autd.send((m, g))?;

    println!("press enter to quit...");
    let mut _s = String::new();
    std::io::stdin().read_line(&mut _s)?;

    // Close controller
    autd.close()?;

    Ok(())
}

そして, これを実行する.

cargo run --release

Linux,macOS使用時の注意

Linux, macOSでは, SOEMを使用するのに管理者権限が必要になる. その場合は,

cargo build --release && sudo ./target/release/autd3_sample

とすること.

依存プログラムのインストール

本チュートリアルではCMakeを使用するので, インストールしておくこと.

AUTD3クライアントプログラムの作成

まず, ターミナルを開き, 適当なディレクトリを用意する.

mkdir autd3-sample
cd autd3-sample

次に, autd3-sample以下にCMakeLists.txt, main.cppファイルを作成する.

└─autd3-sample
        CMakeLists.txt
        main.cpp

次に, CMakeLists.txtを以下のようにする.

cmake_minimum_required(VERSION 3.21)

project(autd3-sample)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.24.0") 
  cmake_policy(SET CMP0135 NEW)
endif()

include(FetchContent)
if(WIN32)
  FetchContent_Declare(
    autd3
    URL https://github.com/shinolab/autd3-cpp/releases/download/v30.0.1/autd3-v30.0.1-win-x64.zip
  )
  FetchContent_Declare(
    autd3-link-soem
    URL https://github.com/shinolab/autd3-cpp-link-soem/releases/download/v30.0.1/autd3-link-soem-v30.0.1-win-x64.zip
  )
elseif(APPLE)
  FetchContent_Declare(
    autd3
    URL https://github.com/shinolab/autd3-cpp/releases/download/v30.0.1/autd3-v30.0.1-macos-aarch64.tar.gz
  )
  FetchContent_Declare(
    autd3-link-soem
    URL https://github.com/shinolab/autd3-cpp-link-soem/releases/download/v30.0.1/autd3-link-soem-v30.0.1-macos-aarch64.tar.gz
  )
else()
  FetchContent_Declare(
    autd3
    URL https://github.com/shinolab/autd3-cpp/releases/download/v30.0.1/autd3-v30.0.1-linux-x64.tar.gz
  )
  FetchContent_Declare(
    autd3-link-soem
    URL https://github.com/shinolab/autd3-cpp-link-soem/releases/download/v30.0.1/autd3-link-soem-v30.0.1-linux-x64.tar.gz
  )
endif()
set(USE_SYSTEM_EIGEN OFF)
FetchContent_MakeAvailable(autd3 autd3-link-soem)

add_executable(main main.cpp)

target_link_libraries(main PRIVATE autd3::autd3 autd3::link::soem)

NOTE: 上記の例では, 依存ライブラリ (Eigen3) を自動的にダウンロードするようになっている. すでにEigen3がインストールされている場合, USE_SYSTEM_EIGENをONにすると, 自動ダウンロードを無効化し, インストール済みのものを使用できる.

また, main.cppを以下のようにする. これは単一焦点にのAM変調をかける場合のソースコードである.

#include <autd3_link_soem.hpp>
#include <iostream>

#include "autd3.hpp"

using namespace autd3;

int main() try {
  auto autd = Controller::open(
      {AUTD3{
          .pos = Point3::origin(),
          .rot = Quaternion::Identity(),
      }},
      link::SOEM(
          [](const uint16_t slave, const link::Status status) {
            std::cout << "slave [" << slave << "]: " << status << std::endl;
            if (status == link::Status::Lost()) exit(-1);
          },
          link::SOEMOption{}));

  const auto firm_version = autd.firmware_version();
  std::copy(firm_version.begin(), firm_version.end(),
            std::ostream_iterator<FirmwareVersion>(std::cout, "\n"));

  autd.send(Silencer{});

  Focus g(autd.center() + Vector3(0, 0, 150), FocusOption{});
  Sine m(150 * Hz, SineOption{});

  autd.send((m, g));

  std::cout << "press enter to finish..." << std::endl;
  std::cin.ignore();

  autd.close();

  return 0;
} catch (std::exception& ex) {
  std::cerr << ex.what() << std::endl;
}

次に, CMakeでビルドする.

mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
cmake --build . --config Release

これで, 実行ファイルが生成されるので, これを実行する.

.\Release\main.exe
sudo ./main

トラブルシューティング

  • anaconda (miniconda) がactivateされている場合に, ビルドエラーになる可能性がある.
    • この場合, buildディレクトリを削除し, conda deactivateを実行したのち再びcmakeを実行する.

まず, ターミナルを開き, 適当なプロジェクトを作成し, AUTD3Sharpライブラリを追加する.

dotnet new console --name autd3-sample
cd autd3-sample
dotnet add package AUTD3Sharp
dotnet add package AUTD3Sharp.Link.SOEM

次に, Program.csを以下のようにする. これは単一焦点にのAM変調をかける場合のソースコードである.

using AUTD3Sharp;
using AUTD3Sharp.Utils;
using AUTD3Sharp.Link;
using AUTD3Sharp.Gain;
using AUTD3Sharp.Modulation;
using static AUTD3Sharp.Units;

using var autd = Controller.Open(
    [new AUTD3(pos: Point3.Origin, rot: Quaternion.Identity)],
    new SOEM(
        (slave, status) =>
            {
                Console.Error.WriteLine($"slave [{slave}]: {status}");
                if (status == Status.Lost)
                    Environment.Exit(-1);
            },
        new SOEMOption()
    )
);

var firmList = autd.FirmwareVersion();
foreach (var firm in firmList)
    Console.WriteLine(firm);

autd.Send(new Silencer());

var g = new Focus(
    pos: autd.Center() + new Vector3(0, 0, 150),
    option: new FocusOption()
);
var m = new Sine(
    freq: 150u * Hz,
    option: new SineOption()
);
autd.Send((m, g));

Console.ReadKey(true);

autd.Close();

そして, これを実行する.

dotnet run -c:Release

Linux,macOS使用時の注意

Linux, macOSでは, SOEMを使用するのに管理者権限が必要な場合がある. その場合は,

sudo dotnet run -c:Release

とすること.

pyautd3ライブラリのインストール

pip install pyautd3
pip install pyautd3_link_soem

次に, main.pyを作成し, 以下のようにする. これは単一焦点にのAM変調をかける場合のソースコードである.

import os

import numpy as np
from pyautd3 import (
    AUTD3,
    Controller,
    Focus,
    FocusOption,
    Hz,
    Silencer,
    Sine,
    SineOption,
)
from pyautd3_link_soem import SOEM, SOEMOption, Status


def err_handler(slave: int, status: Status) -> None:
    print(f"slave [{slave}]: {status}")
    if status == Status.Lost():
        os._exit(-1)


if __name__ == "__main__":
    with Controller.open(
        [AUTD3(pos=[0.0, 0.0, 0.0], rot=[1, 0, 0, 0])],
        SOEM(err_handler=err_handler, option=SOEMOption()),
    ) as autd:
        firmware_version = autd.firmware_version()
        print(
            "\n".join(
                [f"[{i}]: {firm}" for i, firm in enumerate(firmware_version)],
            ),
        )

        autd.send(Silencer())

        g = Focus(
            pos=autd.center() + np.array([0.0, 0.0, 150.0]),
            option=FocusOption(),
        )
        m = Sine(
            freq=150 * Hz,
            option=SineOption(),
        )
        autd.send((m, g))

        _ = input()

        autd.close()

そして, これを実行する.

python main.py

Linux使用時の注意

Linuxでは, SOEMを使用するのに管理者権限が必要になる. その場合は,

sudo setcap cap_net_raw,cap_net_admin=eip <your python path>

とした後, main.pyを実行する.

python main.py

macOS使用時の注意

macOSでは, SOEMを使用するのに管理者権限が必要になる. その場合は,

sudo chmod +r /dev/bpf*

とした後, main.pyを実行する.

python main.py
1

一部機能は未サポート. 詳細はFirmware v10 vs v11を参照.

複数デバイスの接続

AUTD3は複数のデバイスをデイジーチェーン接続して大きな一つのアレイを構成することができる. SDKは複数台を接続したとしても, 透過的に使用できるように設計されている.

SDKで複数台のデバイスを使用する場合はController::open関数の第1引数で接続したデバイスの順にAUTD3構造体を指定する必要がある. ハードウェアの接続方法ははじめに/ハードウェアを参照されたい.

以下では, 2つのデバイスを接続する場合の手順を示す.

並進のみ

例えば, 上図のように配置・接続しており, 図左側のデバイスが1台目, 右側のデバイスが2台目だとする. さらに, グローバル座標を1台目のローカル座標と同じようにとるとすると, コードは以下の通りになる.

use autd3::prelude::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
let link = autd3::link::Nop::new();
let _ =
Controller::open(
    [
        AUTD3 {
            pos: Point3::origin(),
            rot: UnitQuaternion::identity(),
        },
        AUTD3 {
            pos: Point3::new(AUTD3::DEVICE_WIDTH, 0., 0.),
            rot: UnitQuaternion::identity(),
        },
    ],
    link,
)?;
    Ok(())
}
#include<chrono>
#include<autd3.hpp>
#include<autd3/link/nop.hpp>
int main() {
using namespace autd3;
link::Nop link;
Controller::open({AUTD3{
                      .pos = Point3::origin(),
                      .rot = Quaternion::Identity(),
                  },
                  AUTD3{
                      .pos = Point3(AUTD3::DEVICE_WIDTH, 0, 0),
                      .rot = Quaternion::Identity(),
                  }},
                 std::move(link));
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Link;
using AUTD3Sharp.Utils;
var link = new Nop();
Controller.Open([
   new AUTD3(pos: Point3.Origin, rot: Quaternion.Identity),
   new AUTD3(pos: new Point3(AUTD3.DeviceWidth, 0, 0), rot: Quaternion.Identity)
], link)
;
from pyautd3 import AUTD3, Controller
from pyautd3.link.nop import Nop
link = Nop()
Controller.open(
    [
        AUTD3(pos=[0.0, 0.0, 0.0], rot=[1, 0, 0, 0]),
        AUTD3(pos=[AUTD3.DEVICE_WIDTH, 0.0, 0.0], rot=[1, 0, 0, 0]),
    ],
    link,
)

ここで, posはグローバル座標におけるデバイスの位置を表す. なお, AUTD3::DEVICE_WIDTHはデバイスの (基板外形を含めた) 横幅である.

グローバル座標の設定

SDKで使用するグローバル座標の原点や向きは, ユーザーが自由に設定できる.

例えば, 上図のように, グローバル座標を2台目のローカル座標と同じようにとると, コードは以下の通りになる.

use autd3::prelude::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
let link = autd3::link::Nop::new();
let _ =
Controller::open(
    [
        AUTD3 {
            pos: Point3::new(-AUTD3::DEVICE_WIDTH, 0., 0.),
            rot: UnitQuaternion::identity(),
        },
        AUTD3 {
            pos: Point3::origin(),
            rot: UnitQuaternion::identity(),
        },
    ],
    link,
)?;
    Ok(())
}
#include<chrono>
#include<autd3.hpp>
#include<autd3/link/nop.hpp>
int main() {
using namespace autd3;
link::Nop link;
Controller::open(
    {
        AUTD3{
            .pos = Point3(-AUTD3::DEVICE_WIDTH, 0, 0),
            .rot = Quaternion::Identity(),
        },
        AUTD3{
            .pos = Point3::origin(),
            .rot = Quaternion::Identity(),
        },
    },
    std::move(link));
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Link;
using AUTD3Sharp.Utils;
var link = new Nop();
Controller.Open([
   new AUTD3(pos: new Point3(-AUTD3.DeviceWidth, 0, 0), rot: Quaternion.Identity),
   new AUTD3(pos: Point3.Origin, rot: Quaternion.Identity)
], link)
;
from pyautd3 import AUTD3, Controller
from pyautd3.link.nop import Nop
link = Nop()
Controller.open(
    [
        AUTD3(pos=[-AUTD3.DEVICE_WIDTH, 0.0, 0.0], rot=[1, 0, 0, 0]),
        AUTD3(pos=[0.0, 0.0, 0.0], rot=[1, 0, 0, 0]),
    ],
    link,
)

並進と回転

デバイスの回転を指定する場合はrotで指定する. ここで回転はオイラー角, または, クオータニオンで指定する.

例えば, 上図のように配置されており, 下が1台目, 左が2台目で, グローバル座標を1台目のローカル座標と同じだとすると, コードは以下の通りになる.

use autd3::prelude::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
let link = autd3::link::Nop::new();
let _ =
Controller::open(
    [
        AUTD3 {
            pos: Point3::origin(),
            rot: UnitQuaternion::identity(),
        },
        AUTD3 {
            pos: Point3::new(0., 0., AUTD3::DEVICE_WIDTH),
            rot: EulerAngle::ZYZ(0. * rad, PI/2.0 * rad, 0. * rad).into(),
        },
    ],
    link,
)?;
    Ok(())
}
#include<chrono>
#include<autd3.hpp>
#include<autd3/link/nop.hpp>
int main() {
using namespace autd3;
link::Nop link;
Controller::open({AUTD3{
                      .pos = Point3::origin(),
                      .rot = Quaternion::Identity(),
                  },
                  AUTD3{
                      .pos = Point3(0, 0, AUTD3::DEVICE_WIDTH),
                      .rot = EulerAngles::ZYZ(0. * rad, pi / 2.0 * rad,
                                              0. * rad),
                  }},
                 std::move(link));
return 0; }
using System;
using AUTD3Sharp;
using AUTD3Sharp.Link;
using AUTD3Sharp.Utils;
using static AUTD3Sharp.Units;
var link = new Nop();
Controller.Open([
   new AUTD3(pos: Point3.Origin, rot: Quaternion.Identity),
   new AUTD3(
      pos: new Point3(0, 0, AUTD3.DeviceWidth),
      rot: EulerAngles.Zyz(0 * rad, MathF.PI / 2 * rad, 0 * rad))
], link)
;
import numpy as np
from pyautd3 import AUTD3, Controller, EulerAngles, Nop, rad
link = Nop()
Controller.open(
    [
        AUTD3(pos=[0.0, 0.0, 0.0], rot=[1.0, 0.0, 0.0, 0.0]),
        AUTD3(
            pos=[0.0, 0.0, AUTD3.DEVICE_WIDTH],
            rot=EulerAngles.ZYZ(0 * rad, np.pi / 2 * rad, 0 * rad),
        ),
    ],
    link,
)

NOTE: Rust版のみ, 12種類全てのオイラー角が使用できる. それ以外の言語ではXYZ, ZYZのみ.

コンセプト

SDKを構成する主なコンポーネントは以下の通りである.

  • Controller - AUTD3デバイスに対する全ての操作はこれを介して行う.
  • Geometry - Deviceのコンテナ.
    • Device - AUTD3デバイスに対応する. デバイスが現実世界でどのように配置されているかを管理する. Transducerのコンテナ.
    • Transducer - 振動子に対応する. 振動子が現実世界でどこにあるかを管理する.
  • Link - デバイスとのインターフェース.
  • Gain - 各振動子の位相/振幅を管理する.
  • STM - Spatio-Temporal Modulation (STM, 時空間変調) 機能を提供する. 各振動子の位相/振幅データの時間列を管理する.
  • Modulation - AM変調機能を提供するする. 変調データの時間列を管理する.
  • Silencer - 静音化処理を管理する.

ソフトウェアの使用方法は以下の通りである.

まず, 現実世界のAUTD3デバイスの配置を指定し, どのLinkを使用するかを決め, Controllerを開く. 次に, Controllerを介して, Gain (またはSTM), Modulation, Silencerデータをデバイスに送信する.

送信されたデータに基づいたPWM信号が振動子に印加される. 信号が生成されるまでの流れは以下の図の通りである.

信号が生成されるまでの概念図

Gain/STMで指定された振幅データは, Modulationで指定された変調データと順次掛け合わされた後, Silencerに渡される. Gain/STMで指定された位相データは, そのままSilencerに渡される. Silencerは, これらのデータを静音化処理1する. 最後に, Silencerで処理された振幅/位相データに基づきPWM信号が生成され, 振動子に印加される.

なお, 振幅/位相データ, 及び, 変調データはすべてである.

1

詳細はSilencerを参照.

Geometry

GeometryはAUTD3デバイスが現実世界でどのように配置されているかを管理している.

デバイス/振動子のインデックス

デバイスには接続された順に0から始まるインデックスが割り当てられる.

また, 各デバイスは個の振動子が配置されており, ローカルインデックスが割り振られている (はじめに/ハードウェアの「AUTDの表面写真」を参照).

GeometryのAPI

  • num_devices(): 有効なデバイスの数を取得
  • num_transducers(): 有効な全振動子の数を取得
  • center(): 有効な全振動子の中心を取得

なお, GeometryにはControllerから直接アクセスできる.

use autd3::prelude::*;
#[allow(unused_variables)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let autd = Controller::open([AUTD3::default()], autd3::link::Nop::new())?;
let num_dev = autd.num_devices();
let num_tr = autd.num_transducers();
let center = autd.center();
Ok(())
}
#include<chrono>
#include<autd3.hpp>
#include<autd3/link/nop.hpp>
int main() {
using namespace autd3;
auto autd =
Controller::open({AUTD3{}}, link::Nop{});
const auto num_dev = autd.num_devices();
const auto num_tr = autd.num_transducers();
const auto center = autd.center();
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Link;
using AUTD3Sharp.Utils;
using var autd = Controller.Open([new AUTD3()], new Nop());
var numDevices = autd.NumDevices();
var numTransducers = autd.NumTransducers();
var center = autd.Center();
from pyautd3 import AUTD3, Controller
from pyautd3.link.nop import Nop
autd = Controller.open([AUTD3()], Nop())
num_devices = autd.num_devices()
num_transducers = autd.num_transducers()
center = autd.center()

Deviceの取得

GeometryDeviceのコンテナになっており, DeviceTransducerのコンテナになっている.

Deviceを取得するには, インデクサを使用する. あるいは, イテレータを使用することもできる.

use autd3::prelude::*;
#[allow(unused_variables)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let autd = Controller::open([AUTD3::default()], autd3::link::Nop::new())?;
let dev = &autd[0];
for dev in &autd {
    // do something
}
Ok(())
}
#include<chrono>
#include<autd3.hpp>
#include<autd3/link/nop.hpp>
int main() {
using namespace autd3;
auto autd =
Controller::open({AUTD3{}}, link::Nop{});
{
auto dev = autd[0];
}
{
for (auto& dev : autd) {
  // do something
}
}
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Link;
using AUTD3Sharp.Utils;
using var autd = Controller.Open([new AUTD3()], new Nop());
{
var dev = autd[0];
}
foreach (var dev in autd)
{
  // do something
}
from pyautd3 import AUTD3, Controller
from pyautd3.link.nop import Nop
autd = Controller.open([AUTD3()], Nop())
dev = autd[0]
for _dev in autd:
    pass

DeviceのAPI

  • idx(): デバイスのインデックス
  • enable: 有効/無効フラグ. オフにすると, 以降, そのデバイスのデータは更新されなくなる.
    • 更新されなくなるだけで, 出力が止まるわけではないことに注意.
  • sound_speed: 音速の取得/設定. 単位はmm/s. 位相計算などに使用されるため, 可能な限り現実に即した値を設定することをおすすめする. デフォルトの音速はとなっており, これは, およそ摂氏15度での空気の音速に相当する.
  • set_sound_speed_from_temp(temp): 温度temp [℃]から音速を設定. なお, Geometryにも同名の関数があり, それを使用することですべての有効なデバイスに対して温度から音速を設定できる.
  • wavelength(): デバイスが放出する超音波の波長
  • wavenumber(): デバイスが放出する超音波の波数
  • rotation(): デバイスの回転
  • x_direction(): デバイスのx方向ベクトル
  • y_direction(): デバイスのy方向ベクトル
  • axial_direction(): デバイスの軸方向ベクトル (振動子が向く方向)
use autd3::prelude::*;
#[allow(unused_variables)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut autd = Controller::open([AUTD3::default()], autd3::link::Nop::new())?;
let dev = &mut autd[0];
let idx = dev.idx();
dev.enable = false;
dev.sound_speed = 340e3;
dev.set_sound_speed_from_temp(15.);
let wavelength = dev.wavelength();
let wavenumber = dev.wavenumber();
let rotation = dev.rotation();
let x_dir = dev.x_direction();
let y_dir = dev.y_direction();
let axial_dir = dev.axial_direction();
Ok(())
}
#include<chrono>
#include<autd3.hpp>
#include<autd3/link/nop.hpp>
int main() {
using namespace autd3;
auto autd =
Controller::open({AUTD3{}}, link::Nop{});
auto dev = autd[0];
const auto idx = dev.idx();
const auto enable = dev.enable();
dev.set_enable(false);
const auto sound_speed = dev.sound_speed();
dev.set_sound_speed(340e3);
dev.set_sound_speed_from_temp(15.);
const auto wavelength = dev.wavelength();
const auto wavenumber = dev.wavenumber();
const auto rotation = dev.rotation();
const auto x_dir = dev.x_direction();
const auto y_dir = dev.y_direction();
const auto axial_dir = dev.axial_direction();
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Link;
using AUTD3Sharp.Utils;
using var autd = Controller.Open([new AUTD3()], new Nop());
var dev = autd[0];
var idx = dev.Idx();
dev.Enable = false;
dev.SoundSpeed = 340e3f;
dev.SetSoundSpeedFromTemp(15);
var wavelength = dev.Wavelength();
var wavenumber = dev.Wavenumber();
var rotation = dev.Rotation();
var xDir = dev.XDirection();
var yDir = dev.YDirection();
var axialDir = dev.AxialDirection();
from pyautd3 import AUTD3, Controller
from pyautd3.link.nop import Nop
autd = Controller.open([AUTD3()], Nop())
dev = autd[0]
idx = dev.idx()
dev.enable = False
dev.sound_speed = 340e3
dev.set_sound_speed_from_temp(15.0)
wavelength = dev.wavelength()
wavenumber = dev.wavenumber()
rotation = dev.rotation()
x_dir = dev.x_direction()
y_dir = dev.y_direction()
axial_dir = dev.axial_direction()

Transducerの取得

DeviceTransducerのコンテナになっており, Transducerは各振動子の情報を格納している.

Transducerを取得するには, インデクサを使用する. また, イテレータを使用することもできる.

use autd3::prelude::*;
#[allow(unused_variables)] 
fn main() -> Result<(), Box<dyn std::error::Error>> {
let autd = Controller::open([AUTD3::default()], autd3::link::Nop::new())?;
let tr = &autd[0][0];
for tr in &autd[0] {
    // do something
}
Ok(())
}
#include<chrono>
#include<autd3.hpp>
#include<autd3/link/nop.hpp>
int main() {
using namespace autd3;
auto autd =
Controller::open({AUTD3{}}, link::Nop{});
{
auto tr = autd[0][0];
}
{
for (auto& tr : autd[0]) {
  // do something
}
}
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Link;
using AUTD3Sharp.Utils;
using var autd = Controller.Open([new AUTD3()], new Nop());
{
var tr = autd[0][0];
}
foreach (var tr in autd[0])
{
  // do something
}
from pyautd3 import AUTD3, Controller
from pyautd3.link.nop import Nop
autd = Controller.open([AUTD3()], Nop())
tr = autd[0][0]
for _tr in autd[0]:
    pass

TransducerのAPI

以下の情報を取得できる.

  • idx(): 振動子の(ローカル)インデックス
  • dev_idx(): 振動子が属するデバイスのインデックス
  • position(): 振動子の位置
use autd3::prelude::*;
#[allow(unused_variables)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let autd = Controller::open([AUTD3::default()], autd3::link::Nop::new())?;
let tr = &autd[0][0];
let idx = tr.idx();
let dev_idx = tr.dev_idx();
let position = tr.position();
Ok(())
}
#include<autd3.hpp>
#include<autd3/link/nop.hpp>
int main() {
using namespace autd3;
auto autd =
Controller::open({AUTD3{}}, link::Nop{});
const auto tr = autd[0][0];
const auto idx = tr.idx();
const auto dev_idx = tr.dev_idx();
const auto position = tr.position();
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Link;
using AUTD3Sharp.Utils;
using var autd = Controller.Open([new AUTD3()], new Nop());
var tr = autd[0][0];
var trIdx = tr.Idx();
var devIdx = tr.DevIdx();
var position = tr.Position();
from pyautd3 import AUTD3, Controller
from pyautd3.link.nop import Nop
autd = Controller.open([AUTD3()], Nop())
tr = autd[0][0]
idx = tr.idx()
dev_idx = tr.dev_idx()
position = tr.position()

Link

LinkはAUTD3デバイスとのインターフェースである. 以下の中から一つを選択する必要がある.

TwinCAT

TwinCATはPCでEherCATを使用する際の唯一の公式の方法である. TwinCATはWindowsのみをサポートする非常に特殊なソフトウェアであり, Windowsを半ば強引にリアルタイム化する.

また, 特定のネットワークコントローラが求められるため, 対応するネットワークコントローラの一覧を確認すること.

Note: 或いは, TwinCATのインストール後に, C:/TwinCAT/3.1/Driver/System/TcI8254x.infに対応するデバイスのVendor IDとDevice IDが書かれているので,「デバイスマネージャー」→「イーサネットアダプタ」→「プロパティ」→「詳細」→「ハードウェアID」と照らし合わせることでも確認できる.

上記以外のネットワークコントローラでも動作する場合があるが, その場合, 正常な動作とリアルタイム性は保証されない.

事前準備

TwinCATのインストール

前提として, TwinCATはHyper-VやVirtual Machine Platformと共存できない. そのため, これらの機能を無効にする必要がある. これには, 例えば, PowerShellを管理者権限で起動し,

Disable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V-Hypervisor
Disable-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform

と打ち込めば良い.

また, Windows 11の場合, 仮想化ベースのセキュリティ機能もオフにする必要がある. 「Windows セキュリティ」→ 「デバイス セキュリティ」→「コア分離」→「メモリ整合性」をオフにする.

まず, TwinCAT XAEを公式サイトからダウンロードする. ダウンロードには登録 (無料) が必要になる.

ダウンロードしたインストーラを起動し, 指示に従う. この時, TwinCAT XAE Shell installにチェックを入れ, Visual Studio Integrationのチェックを外すこと.

インストール後に再起動し, C:/TwinCAT/3.1/System/win8settick.batを管理者権限で実行し, 再び再起動する.

AUTD3 Serverのインストール

TwinCATのLinkを使うには, まず, AUTD3 Serverをインストールする必要がある. GitHub Releasesにてインストーラを配布しているので, これをダウンロードし, 指示に従ってインストールする.

NOTE: 必ず, 使用するソフトウェアのバージョンに合わせたAUTD Serverを使用すること.

NOTE: CLI版もある.

AUTD3 Serverを実行すると, 以下のような画面になるので, TwinCATタブを開く.

初回の追加作業

初回のみ, 以下の作業が必要になる.

まず, 「Copy AUTD.xml」ボタンを押す. ここで, 「AUTD.xml is successfully copied」のようなメッセージが出れば成功である.

次に, 「Open XAE Shell」ボタンを押し, XAE Shellを開く. TwinCAT XAE Shell上部メニューから「TwinCAT」→「Show Realtime Ethernet Compatible Devices」を開き「Compatible devices」の中の対応デバイスを選択し, Installをクリックする. 「Installed and ready to use devices (realtime capable)」にインストールされたアダプタが表示されていれば成功である.

なお,「Compatible devices」に何も表示されていない場合はそのPCのイーサネットデバイスはTwinCATに対応していない. 「Incompatible devices」の中のドライバもインストール自体は可能で, インストールすると「Installed and ready to use devices (for demo use only)」と表示される. この場合, 使用できるが動作保証はない.

AUTD Serverの実行

AUTD3とPCを接続し, AUTD3の電源が入った状態で, 「Run」ボタンを押す. このとき, 「Client IP address」の欄は空白にしておくこと.

下の画面のように, AUTD3デバイスが見つかった旨のメッセージが出れば成功である.

なお, TwinCATはPCの電源を切る, スリープモードに入る等で接続が途切れるので, その都度実行し直すこと.

ライセンス

初回はライセンス関係のエラーが出るので, XAE Shellで「Solution Explorer」→「SYSTEM」→「License」を開き, 「7 Days Trial License …」をクリックし, 画面に表示される文字を入力する. なお, ライセンスは7日間限定のトライアルライセンスだが, 切れたら再び同じ作業を行うことで再発行できる. ライセンスを発行し終わったら, “TwinCAT XAE Shell“を閉じて, 再び実行する.

TwinCATリンク

Install

cargo add autd3-link-twincat
target_link_libraries(<TARGET> PRIVATE autd3::link::twincat)

メインライブラリに含まれている.

メインライブラリに含まれている.

メインライブラリに含まれている.

APIs

use autd3_link_twincat::TwinCAT;

fn main() {
let _ = 
TwinCAT::new();
}
#include "autd3/link/twincat.hpp"

int main() {
using namespace autd3;
link::TwinCAT();
return 0; }
using AUTD3Sharp.Link;

new TwinCAT();
from pyautd3.link.twincat import TwinCAT

TwinCAT()

トラブルシューティング

大量のデバイスを使用しようとすると, 下の図のようなエラーが発生することがある.

9台のAUTD3デバイスを使用した際のTwinCATエラー

この場合は, AUTD3 ServerSync0 cycle timeSend task cycle timeの値を増やし, AUTD Serverを再び実行する. これらのオプションの値はデフォルトでそれぞれになっている.

どの程度の値にすればいいかは接続する台数による. エラーが出ない中で可能な限り小さな値が望ましい. 例えば, 9台の場合は程度の値にしておけば動作するはずである.

SOEM

SOEMは有志が開発しているオープンソースのEherCAT Masterライブラリである. TwinCATとは異なりリアルタイム性は保証されない. そのため, 基本的にTwinCATを使用することを推奨する. SOEMを使用するのはやむを得ない理由があるか, 開発時のみに限定するべきである. 一方, SOEMはクロスプラットフォームで動作し, インストールも単純という利点がある.

Windowsの場合は, npcapを「WinPcap API compatible mode」でインストールしておくこと. Linux/macOSの場合は, 特に準備は必要ない.

Install

cargo add autd3-link-soem
if(WIN32)
  FetchContent_Declare(
    autd3-link-soem
    URL https://github.com/shinolab/autd3-cpp-link-soem/releases/download/v32.0.1/autd3-link-soem-v32.0.1-win-x64.zip
  )
elseif(APPLE)
  FetchContent_Declare(
    autd3-link-soem
    URL https://github.com/shinolab/autd3-cpp-link-soem/releases/download/v32.0.1/autd3-link-soem-v32.0.1-macos-aarch64.tar.gz
  )
else()
  FetchContent_Declare(
    autd3-link-soem
    URL https://github.com/shinolab/autd3-cpp-link-soem/releases/download/v32.0.1/autd3-link-soem-v32.0.1-linux-x64.tar.gz
  )
endif()
FetchContent_MakeAvailable(autd3-link-soem)
target_link_libraries(<TARGET> PRIVATE autd3::link::soem)
dotnet add package AUTD3Sharp.Link.SOEM

https://github.com/shinolab/AUTD3Sharp.Link.SOEM.git#upm/latestをUnity Package Managerで追加する.

pip install pyautd3_link_soem

APIs

第1引数にはエラーが起きたときのコールバック関数を, 第2引数にはオプションを指定する.

#[cfg(target_os = "windows")]
use autd3_link_soem::ProcessPriority;
use autd3_link_soem::{Status, SyncMode, ThreadPriority, TimerStrategy, SOEM, SOEMOption};
use std::num::NonZeroUsize;
use std::time::Duration;

fn main() {
let _ = 
SOEM::new(
    |slave, status| {
        eprintln!("slave [{}]: {}", slave, status);
        if status == Status::Lost {
            std::process::exit(-1);
        }
    },
    SOEMOption {
        buf_size: NonZeroUsize::new(32).unwrap(),
        timer_strategy: TimerStrategy::SpinSleep,
        sync_mode: SyncMode::DC,
        ifname: String::new(),
        state_check_interval: Duration::from_millis(100),
        sync0_cycle: Duration::from_millis(1),
        send_cycle: Duration::from_millis(1),
        thread_priority: ThreadPriority::Max,
        #[cfg(target_os = "windows")]
        process_priority: ProcessPriority::High,
        sync_tolerance: Duration::from_micros(1),
        sync_timeout: Duration::from_secs(10),
    },
);
}
#include <iostream>
#include <autd3_link_soem.hpp>

int main() {
using namespace autd3;
link::SOEM(
    [](const uint16_t slave, const link::Status status) {
      std::cout << "slave [" << slave << "]: " << status << std::endl;
      if (status == link::Status::Lost()) {
        exit(-1);
      }
    },
    link::SOEMOption{
        .buf_size = 32,
        .timer_strategy = link::TimerStrategy::SpinSleep,
        .sync_mode = link::SyncMode::DC,
        .ifname = "",
        .state_check_interval = std::chrono::milliseconds(100),
        .sync0_cycle = std::chrono::milliseconds(1),
        .send_cycle = std::chrono::milliseconds(1),
        .thread_priority = link::ThreadPriority::Max(),
        .process_priority = link::ProcessPriority::High,
        .sync_tolerance = std::chrono::microseconds(1),
        .sync_timeout = std::chrono::seconds(10),

    });
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Link;

using AUTD3Sharp.Utils;
new SOEM(
    errHandler: (slave, status) =>
    {
        Console.Error.WriteLine($"slave [{slave}]: {status}");
        if (status == Status.Lost)
            Environment.Exit(-1);
    },
    option: new SOEMOption
    {
        BufSize = 32,
        TimerStrategy = TimerStrategy.SpinSleep,
        SyncMode = SyncMode.DC,
        Ifname = "",
        StateCheckInterval = Duration.FromMillis(100),
        Sync0Cycle = Duration.FromMillis(1),
        SendCycle = Duration.FromMillis(1),
        ThreadPriority = AUTD3Sharp.Link.ThreadPriority.Max,
        ProcessPriority = ProcessPriority.High,
        SyncTolerance = Duration.FromMicros(1),
        SyncTimeout = Duration.FromSecs(10),
    }
);
import os
from pyautd3 import Duration
from pyautd3_link_soem import (
    SOEM,
    ProcessPriority,
    SOEMOption,
    Status,
    SyncMode,
    ThreadPriority,
    TimerStrategy,
)


def err_handler(slave: int, status: Status) -> None:
    print(f"slave [{slave}]: {status}")
    if status == Status.Lost():
        os._exit(-1)


SOEM(
    err_handler=err_handler,
    option=SOEMOption(
        buf_size=32,
        timer_strategy=TimerStrategy.SpinSleep,
        sync_mode=SyncMode.DC,
        ifname="",
        state_check_interval=Duration.from_millis(100),
        sync0_cycle=Duration.from_millis(1),
        send_cycle=Duration.from_millis(1),
        thread_priority=ThreadPriority.Max,
        process_priority=ProcessPriority.High,  # only available on Windows
        sync_tolerance=Duration.from_micros(1),
        sync_timeout=Duration.from_secs(10),
    ),
)

SOEMリンクで指定できるオプションは以下の通りである. デフォルト値は上記の通り.

  • buf_size: 送信キューバッファサイズ. 通常は変更する必要はない.
  • timer_strategy: タイマーの戦略
    • StdSleep : 標準ライブラリのsleepを用いる
    • SpinSleep : spin_sleep crateを用いる. OSネイティブのスリープ (Windowsの場合はWaitableTimer) とスピンループを組み合わせ.
    • SpinWait : スピンループを用いる. 高解像度だが, CPU負荷が高い.
  • sync_mode: 同期モード
  • ifname: ネットワークインタフェース名. 空白の場合はAUTD3デバイスが接続されているネットワークインタフェースを自動的に選択する.
  • state_check_interval: エラーが出ているかどうかを確認する間隔
  • sync0_cycle: 同期信号の周期
  • send_cycle: 送信サイクル
    • SOEMも大量のデバイスを接続すると挙動が不安定になる場合がある1. このときは, sync0_cyclesend_cycleの値を増やす. これら値はエラーが出ない中で, 可能な限り小さな値が望ましい. どの程度の値にすべきかは接続している台数に依存する. 例えば, 9台の場合は程度の値にしておけば動作するはずである.
  • thread_priority: スレッドの優先度
  • process_priority: (Windowsのみ) プロセスの優先度
  • sync_tolerance: 同期許容レベル. 初期化時, 各デバイスのシステム時間差がこの値以下になるまで待機する. 以下のタイムアウト時間が経過しても同期が完了しない場合はエラーとなる. この値を変更することは推奨されない.
  • sync_timeout: 同期タイムアウト. 上記のシステム時間差測定のタイムアウト時間.
1

TwinCATよりは緩く, 普通に動くこともある.

Simulator

Simulator linkはAUTDシミュレータを使用する際に使うLinkである.

このlinkの使用の前に, AUTDシミュレータを起動しておく必要がある.

Install

cargo add autd3-link-simulator --features blocking
target_link_libraries(<TARGET> PRIVATE autd3::link::simulator)

メインライブラリに含まれている.

メインライブラリに含まれている.

メインライブラリに含まれている.

APIs

SimulatorのコンストラクタにはAUTDシミュレータのIPアドレスとポート番号を指定する.

use autd3_link_simulator::Simulator;

fn main() -> Result<(), Box<dyn std::error::Error>> {
let _ = 
Simulator::new("127.0.0.1:8080".parse()?);
Ok(())
}
#include "autd3/link/simulator.hpp"

int main() {
using namespace autd3;
link::Simulator("127.0.0.1:8080");
return 0; }
using System.Net;
using AUTD3Sharp.Link;

new Simulator(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080));
from pyautd3.link.simulator import Simulator

Simulator("127.0.0.1:8080")

RemoteTwinCAT

前述の通り, AUTD3とTwinCATを使う場合はWindows OSと特定のネットワークアダプタが必要になる. Windows以外のPCで開発したい場合は, RemoteTwinCAT linkを用いてLinux/macOSから遠隔でTwinCATを操作することができる.

Install

cargo add autd3-link-twincat --features remote
target_link_libraries(<TARGET> PRIVATE autd3::link::twincat)

メインライブラリに含まれている.

メインライブラリに含まれている.

メインライブラリに含まれている.

セットアップ

RemoteTwinCATを使用する場合はPCを2台用意する必要がある. この時, 片方のPCはTwinCATが使えるPCである必要がある. このPCをここでは“サーバ“と呼ぶ. 一方, 開発側のPC, 即ちSDKを使用する側は特に制約はなく, サーバと同じLANに繋がっていれば良い, こちらをここでは“クライアント“と呼ぶ.

まず, サーバとAUTDデバイスを接続する. この時使うLANのアダプタはTwinCATと同じく, TwinCAT対応のアダプタである必要がある. また, サーバとクライアントを別のLANで繋ぐ. こちらのLANアダプタはTwinCAT対応である必要はない1. そして, サーバとクライアント間のLANのIPを確認しておく. ここでは例えば, サーバ側が172.16.99.104, クライアント側が172.16.99.62だったとする. 次に, サーバでAUTD Serverを起動する. この時, Client IP addressにクライアントのIPアドレス (この例だと172.16.99.62) を指定する.

右側の画面に, 「Server AmsNetId」と「Client AmsNetId」が表示されるので, これをメモっておく.

NOTE: 「Server AmsNetId」の最初の4桁は必ずしもServerのIPアドレスを意味しているわけではないので注意されたい.

APIs

RemoteTwinCATのコンストラクタには「Server AmsNetId」を指定する.

また, server_ipclient_ams_net_idでサーバーのIPアドレスとクライアントのNetIdを指定する. これらは省略することも可能だが, 基本的には指定することを推奨する.

use autd3_link_twincat::remote::{RemoteTwinCAT, RemoteTwinCATOption};

fn main() {
let _ = 
RemoteTwinCAT::new("172.16.99.111.1.1", RemoteTwinCATOption {
    server_ip: "172.16.99.104".to_string(),
    client_ams_net_id: "172.16.99.62.1.1".to_string(),
});
}
#include "autd3/link/twincat.hpp"

int main() {
using namespace autd3;
link::RemoteTwinCAT("172.16.99.111.1.1",
                    link::RemoteTwinCATOption{
                        .server_ip = "172.16.99.104",
                        .client_ams_net_id = "172.16.99.62.1.1"});
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Link;

new RemoteTwinCAT(
        serverAmsNetId: "172.16.99.111.1.1",
        option: new RemoteTwinCATOption
        {
                ServerIp = "172.16.99.104",
                ClientAmsNetId = "172.16.99.62.1.1"
        }
);
from pyautd3.link.twincat import RemoteTwinCAT, RemoteTwinCATOption

RemoteTwinCAT(
    server_ams_net_id="172.16.99.111.1.1",
    option=RemoteTwinCATOption(
        server_ip="172.16.99.104",
        client_ams_net_id="172.16.99.62.1.1",
    ),
)

ファイアウォール

TCP関係のエラーが出る場合は, ファイアウォールでADSプロトコルがブロックされている可能性がある. その場合は, ファイアウォールの設定でTCP/UDPの48898番ポートの接続を許可する.

1

無線LANでも可

RemoteSOEM

このLinkはSOEMを動かすサーバーPCとユーザプログラムを動かすクライアントPCを分離するためのものである.

Install

cargo add autd3-link-soem --features "remote blocking"
if(WIN32)
  FetchContent_Declare(
    autd3-link-soem
    URL https://github.com/shinolab/autd3-cpp-link-soem/releases/download/v32.0.1/autd3-link-soem-v32.0.1-win-x64.zip
  )
elseif(APPLE)
  FetchContent_Declare(
    autd3-link-soem
    URL https://github.com/shinolab/autd3-cpp-link-soem/releases/download/v32.0.1/autd3-link-soem-v32.0.1-macos-aarch64.tar.gz
  )
else()
  FetchContent_Declare(
    autd3-link-soem
    URL https://github.com/shinolab/autd3-cpp-link-soem/releases/download/v32.0.1/autd3-link-soem-v32.0.1-linux-x64.tar.gz
  )
endif()
FetchContent_MakeAvailable(autd3-link-soem)
target_link_libraries(<TARGET> PRIVATE autd3::link::soem)
dotnet add package AUTD3Sharp.Link.SOEM

https://github.com/shinolab/AUTD3Sharp.Link.SOEM.git#upm/latestをUnity Package Managerで追加する.

pip install pyautd3_link_soem

セットアップ

RemoteSOEMを使用する場合はPCを2台用意する必要がある. この時, 片方のPCはSOEMが使えるPCである必要がある. このPCをここでは“サーバ“と呼ぶ. 一方, 開発側のPC, 即ちSDKを使用する側は特に制約はなく, サーバと同じLANに繋がっていれば良い, こちらをここでは“クライアント“と呼ぶ.

まず, サーバとAUTDデバイスを接続する. また, サーバとクライアントを別のLANで繋ぐ1. そして, サーバとクライアント間のLANのIPを確認しておく. ここでは例えば, サーバ側が172.16.99.104, クライアント側が172.16.99.62だったとする.

AUTD Server

RemoteSOEMを使用する場合, サーバにAUTD Serverをインストールする必要がある. GitHub Releasesにてインストーラを配布しているので, これをダウンロードし, 指示に従ってインストールする.

AUTD Serverを実行すると, 以下のような画面になるので, SOEMタブを開く.

ポートに適当なポート番号を指定し, Runボタンを押す.

AUTD3デバイスが見つかり, クライアントとの接続待ちである旨のメッセージが表示されれば成功である.

なお, AUTD ServerではSOEMと同等のオプションを指定できる.

APIs

RemoteSOEMのコンストラクタでは, <サーバのIP:ポート>を指定する.

use autd3_link_soem::RemoteSOEM;

fn main() -> Result<(), Box<dyn std::error::Error>> {
let _ = 
RemoteSOEM::new("172.16.99.104:8080".parse()?);
Ok(())
}
#include <autd3_link_soem.hpp>

int main() {
using namespace autd3;
link::RemoteSOEM("172.16.99.104:8080");
return 0; }
using System.Net;
using AUTD3Sharp.Link;

new RemoteSOEM(new IPEndPoint(IPAddress.Parse("172.16.99.104"), 8080))
;
from pyautd3_link_soem import RemoteSOEM

RemoteSOEM("172.16.99.104:8080")

ファイアウォール

TCP関係のエラーが出る場合は, ファイアウォールでブロックされている可能性がある. その場合は, ファイアウォールの設定でTCP/UDPの指定したポートの接続を許可する.

1

無線LANでも可

Controller

ここでは, Controllerに存在するAPIを紹介する.

fpga_state

FPGAの状態を取得する. これを使用する前に, ReadsFPGAStateで状態取得を有効化しておく必要がある.

use autd3::prelude::*;
#[allow(unused_variables)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut autd = Controller::open([AUTD3::default()], autd3::link::Nop::new())?;
autd.send(ReadsFPGAState::new(|_dev| true))?;

let info = autd.fpga_state()?;
Ok(())
}
#include<autd3.hpp>
#include<autd3/link/nop.hpp>
int main() {
using namespace autd3;
auto autd =
Controller::open({AUTD3{}}, link::Nop{});
autd.send(ReadsFPGAState([](const auto&) { return true; }));

const auto info = autd.fpga_state();
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Link;
using AUTD3Sharp.Utils;
using var autd = Controller.Open([new AUTD3()], new Nop());
autd.Send(new ReadsFPGAState(_ => true));

var info = autd.FPGAState();
from pyautd3 import Controller, AUTD3, ReadsFPGAState
from pyautd3.link.nop import Nop
autd = Controller.open([AUTD3()], Nop())
autd.send(ReadsFPGAState(lambda _: True))

info = autd.fpga_state()

ReadsFPGAStateコンストラクタの引数はFn(&Device) -> boolで, デバイス毎に状態取得を有効化するかどうかを指定する.

有効化していないデバイスに対してfpga_stateNoneを返す.

FPGAの状態としては, 現在以下の情報が取得できる.

  • is_thermal_assert: ファン制御用の温度センサがアサートされているかどうか
  • current_mod_segment: 現在のModulation Segment
  • current_stm_segment: 現在のFociSTM/GainSTM Segment
  • current_gain_segment: 現在のGain Segment
  • is_gain_mode: 現在Gainが使用されているかどうか
  • is_stm_mode: 現在FociSTM/GainSTMが使用されているかどうか

send

デバイスにデータを送信する.

データは単体か2つのみ同時に送信することができる.

group_send

group_send関数を使用すると, デバイスをグルーピングすることができる.

use autd3::prelude::*;
use autd3::gain::IntoBoxedGain;

use std::collections::HashMap;
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut autd = Controller::open(
        [AUTD3::default(), AUTD3::default()],
        autd3::link::Nop::new(),
    )?;
    let x = 0.;
    let y = 0.;
    let z = 0.;
autd.group_send(
    |dev| match dev.idx() {
        0 => Some("focus"),
        1 => Some("null"),
        _ => None,
    },
    HashMap::from([
        (
            "focus",
            Focus {
                pos: Point3::new(x, y, z),
                option: Default::default(),
            }
            .into_boxed(),
        ),
        ("null", Null {}.into_boxed()),
    ]),
)?;
    Ok(())
}
#include<chrono>
#include<autd3.hpp>
#include<autd3/link/nop.hpp>
int main() {
using namespace autd3;
auto autd =
Controller::open({AUTD3{}}, link::Nop{});
const auto x = 0.0;
const auto y = 0.0;
const auto z = 0.0;
autd.group_send(
    [](const Device& dev) -> std::optional<const char*> {
      if (dev.idx() == 0) {
        return "null";
      } else if (dev.idx() == 1) {
        return "focus";
      } else {
        return std::nullopt;
      }
    },
    std::unordered_map<const char*, std::shared_ptr<driver::Datagram>>{
        {"focus",
         std::make_shared<Focus>(Focus(Point3(x, y, z), FocusOption{}))},
        {"null", std::make_shared<Null>()}});
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Link;
using AUTD3Sharp.Gain;
using AUTD3Sharp.Modulation;
using AUTD3Sharp.Utils;
using var autd = Controller.Open([new AUTD3()], new Nop());
var x = 0.0f;
var y = 0.0f;
var z = 0.0f;
autd.GroupSend(dev =>
    {
        return dev.Idx() switch
        {
            0 => "null",
            1 => "focus",
            _ => null
        };
    },
    new GroupDictionary {
        { "null", new Null() },
        { "focus", new Focus(pos: new Point3(x, y, z), option: new FocusOption()) }
    }
);
from pyautd3 import AUTD3, Controller, Device
from pyautd3.gain import Focus, FocusOption, Null
from pyautd3.link.nop import Nop
autd = Controller.open([AUTD3()], Nop())
x = 0.0
y = 0.0
z = 0.0
def key_map(dev: Device) -> str | None:
    if dev.idx == 0:
        return "null"
    if dev.idx == 1:
        return "focus"
    return None


autd.group_send(
    key_map=key_map,
    data_map={"null": Null(), "focus": Focus(pos=[x, y, z], option=FocusOption())},
)

gain::Groupとは異なり, 通常のsendで送信できるデータなら何でも使用できる. ただし, デバイス単位でしかグルーピングできない.

NOTE: このサンプルでは, キーとして文字列を使用しているが, HashMapのキーとして使用できるものなら何でも良い.

sender

送信時の設定をsender経由で指定できる.

use std::time::Duration;
use autd3::prelude::*;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut autd = Controller::open([AUTD3::default()], autd3::link::Nop::new())?;
let mut sender = autd.sender(SenderOption {
    send_interval: Duration::from_millis(1),
    receive_interval: Duration::from_millis(1),
    timeout: None,
    parallel: ParallelMode::Auto,
    sleeper: SpinSleeper::default(),
});
let d = Null {};
sender.send(d)?;
Ok(())
}
#include<chrono>
#include<autd3.hpp>
#include<autd3/link/nop.hpp>
int main() {
using namespace autd3;
auto autd = Controller::open({AUTD3{}}, link::Nop{});
auto sender = autd.sender(SenderOption{
    .send_interval = std::chrono::milliseconds(1),
    .receive_interval = std::chrono::milliseconds(1),
    .timeout = std::nullopt,
    .parallel = ParallelMode::Auto,
    .sleeper = SpinSleeper(),
});
const Null d;
sender.send(d);
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Link;
using AUTD3Sharp.Gain;
using AUTD3Sharp.Utils;
using var autd = Controller.Open([new AUTD3()], new Nop());
var sender = autd.Sender(
    new SenderOption
    {
        SendInterval = Duration.FromMillis(1),
        ReceiveInterval = Duration.FromMillis(1),
        Timeout = null,
        Parallel = ParallelMode.Auto,
        Sleeper = new SpinSleeper()
    }
);
var d = new Null();
sender.Send(d);
from pyautd3 import AUTD3, Controller, Duration, Null, SenderOption, SpinSleeper, ParallelMode
from pyautd3.link.nop import Nop
autd = Controller.open([AUTD3()], Nop())
sender = autd.sender(
    SenderOption(
        send_interval=Duration.from_millis(1),
        receive_interval=Duration.from_millis(1),
        timeout=None,
        parallel=ParallelMode.Auto,
        sleeper=SpinSleeper(),
    )
)
d = Null()
sender.send(d)

ここで,

  • send_interval: 送信間隔
  • receive_interval: 受信間隔
  • timeout: タイムアウト時間. 詳細はタイムアウトについてを参照
  • parallel: 並列計算モード. 詳細は並列計算についてを参照
  • sleeper: 送受信間隔を調整する構造体
    • SpinSleeper: spin_sleepを使用
    • StdSleeper: std::thread::sleepを使用
    • WaitableSleeper: (Windowsのみ) Waitable Timerを使用

であり, デフォルト値は上記の通り.

なお, Controller::send, Controller::group_sendはデフォルトのSenderOptionを使用した場合と等価である.

タイムアウトについて

タイムアウトの値が

  • 0より大きい場合, 送信データがデバイスで処理されるか, 指定したタイムアウト時間が経過するまで待機する. 送信データがデバイスで処理されたのが確認できなかった場合にエラーを返す.
  • 0の場合, send関数は送信データがデバイスで処理されたかどうかのチェックを行わない.

確実にデータを送信したい場合はこれを適当な値に設定しておくことをおすすめする.

SenderOptionで指定しない場合, 以下に示す各データのデフォルト値が使用される.

タイムアウト値
Clear/GPIOOutputs/
ForceFan/PhaseCorrection/
PulseWidthEncoder/ReadsFPGAState/
SwapSegment/Silencer/
Synchronize/FociSTM/
GainSTM/Modulation
Gain

複数をまとめて送信する場合は, それぞれのデータのタイムアウト値の最大値が使用される.

並列計算について

各データの内部での計算は, デバイス単位で並列に実行することができる.

ParallelMode::Onを指定すると並列計算を有効化, ParallelMode::Offを指定すると無効化する.

ParallelMode::Autoの場合, 有効なデバイスの数が以下に示す各データの並列計算スレッショルド値を超える場合に並列計算が有効化される.

並列計算スレッショルド値
Clear/GPIOOutputs/
ForceFan/PhaseCorrection/
ReadsFPGAState/SwapSegment/
Silencer/Synchronize/
FociSTM (焦点数が4000未満)/
Modulation
18446744073709551615
PulseWidthEncoder/
FociSTM (焦点数が4000以上)/
/GainSTM/Gain
4

Gain

Gainは各振動子の位相/振幅を管理する構造体の総称であり, Gainを送信することで各振動子の位相/振幅を設定することができる. SDKにはデフォルトでいくつかの種類の音場を生成するためのGainが用意されている.

  • Null ‐ 何も出力しない
  • Focus - 単一焦点
  • Bessel - ベッセルビーム
  • Plane - 平面波
  • Uniform - すべての振動子を同じ位相/振幅で駆動
  • Custom - ユーザーが自由に位相/振幅を指定できる
  • Group - 振動子をグループ化して, 各グループ毎に異なるGainを適用
  • Holo - 多焦点音場
  • Cache - Gainの計算結果をキャッシュする

Null

Source

Nullは振幅0のGainである.

use autd3::prelude::*;
fn main() {
let _ = 
Null {};
}
#include<autd3.hpp>
int main() {
using namespace autd3;
Null{};
return 0; }
using AUTD3Sharp.Gain;
new Null();
from pyautd3 import Null
Null()

Focus

Source

Focusは単一焦点を生成する.

use autd3::prelude::*;
fn main() {
let x = 0.;
let y = 0.;
let z = 0.;
let _ = 
Focus {
    pos: Point3::new(x, y, z),
    option: FocusOption {
        intensity: EmitIntensity::MAX,
        phase_offset: Phase::ZERO,
    },
};
}
#include<autd3.hpp>
int main() {
using namespace autd3;
const auto x = 0.0;
const auto y = 0.0;
const auto z = 0.0;
Focus(Point3(x, y, z),
      FocusOption{
          .intensity = std::numeric_limits<EmitIntensity>::max(),
          .phase_offset = Phase::zero(),
      });
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Utils;
using AUTD3Sharp.Gain;
var x = 0.0f;
var y = 0.0f;
var z = 0.0f;
new Focus(
    pos: new Point3(x, y, z),
    option: new FocusOption
    {
        Intensity = EmitIntensity.Max,
        PhaseOffset = Phase.Zero
    }
);
from pyautd3 import EmitIntensity, Focus, FocusOption, Phase

x = 1.0
y = 0.0
z = 0.0
Focus(
    pos=[x, y, z],
    option=FocusOption(
        intensity=EmitIntensity.MAX,
        phase_offset=Phase.ZERO,
    ),
)

オプションにて, 出力振幅と位相オフセットを指定できる. デフォルト値は上記の通り.

Bessel

Source

BesselではBessel beamを生成する. このGainは長谷川らの論文1に基づく.

use autd3::prelude::*;
fn main() {
let x = 0.;
let y = 0.;
let z = 0.;
let nx = 0.;
let ny = 0.;
let nz = 0.;
let theta = 0. * rad;
let _ = 
Bessel {
    pos: Point3::new(x, y, z),
    dir: UnitVector3::new_normalize(Vector3::new(nx, ny, nz)),
    theta,
    option: BesselOption {
        intensity: EmitIntensity::MAX,
        phase_offset: Phase::ZERO,
    },
};
}
#include<autd3.hpp>
int main() {
using namespace autd3;
const auto x = 0.0;
const auto y = 0.0;
const auto z = 0.0;
const auto nx = 0.0;
const auto ny = 0.0;
const auto nz = 1.0;
const auto theta = 0.0;
Bessel(Point3(x, y, z), Vector3(nx, ny, nz), theta* rad,
       BesselOption{
           .intensity = std::numeric_limits<EmitIntensity>::max(),
           .phase_offset = Phase::zero(),
       });
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Utils;
using AUTD3Sharp.Gain;
using static AUTD3Sharp.Units;
var x = 0.0f;
var y = 0.0f;
var z = 0.0f;
var nx = 0.0f;
var ny = 0.0f;
var nz = 1.0f;
var theta = 0.0f;
new Bessel(
    pos: new Point3(x, y, z),
    dir: new Vector3(nx, ny, nz),
    theta: theta * rad,
    option: new BesselOption
    {
        Intensity = EmitIntensity.Max,
        PhaseOffset = Phase.Zero
    }
);
from pyautd3 import Bessel, BesselOption, EmitIntensity, Phase, rad

x = 0.0
y = 0.0
z = 0.0
nx = 1.0
ny = 0.0
nz = 0.0
theta = 0.0
Bessel(
    pos=[x, y, z],
    direction=[nx, ny, nz],
    theta=theta * rad,
    option=BesselOption(
        intensity=EmitIntensity.MAX,
        phase_offset=Phase.ZERO,
    ),
)

ここで, posはビームを生成する仮想円錐 (下図の点線) の頂点であり, dirはビームの方向, thetaはビームに垂直な面とビームを生成する仮想円錐の側面となす角度である (下図の).

Bessel beam (長谷川らの論文より引用)

オプションにて, 出力振幅と位相オフセットを指定できる. デフォルト値は上記の通り.

1

Hasegawa, Keisuke, et al. “Electronically steerable ultrasound-driven long narrow air stream.” Applied Physics Letters 111.6 (2017): 064104.

Plane

Source

Planeは平面波を出力する.

use autd3::prelude::*;
fn main() {
let nx = 0.;
let ny = 0.;
let nz = 0.;
let _ = 
Plane {
    dir: UnitVector3::new_normalize(Vector3::new(nx, ny, nz)),
    option: PlaneOption {
        intensity: EmitIntensity::MAX,
        phase_offset: Phase::ZERO,
    },
};
}
#include<autd3.hpp>
int main() {
using namespace autd3;
const auto nx = 0.0;
const auto ny = 0.0;
const auto nz = 1.0;
Plane(Vector3(nx, ny, nz),
      PlaneOption{
          .intensity = std::numeric_limits<EmitIntensity>::max(),
          .phase_offset = Phase::zero(),
      });
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Utils;
using AUTD3Sharp.Gain;
var nx = 0.0f;
var ny = 0.0f;
var nz = 1.0f;
new Plane(
    dir: new Vector3(nx, ny, nz),
    option: new PlaneOption
    {
        Intensity = EmitIntensity.Max,
        PhaseOffset = Phase.Zero
    }
);
from pyautd3 import EmitIntensity, Phase, Plane, PlaneOption
nx = 1.0
ny = 0.0
nz = 0.0
Plane(
    direction=[nx, ny, nz],
    option=PlaneOption(
        intensity=EmitIntensity.MAX,
        phase_offset=Phase.ZERO,
    ),
)

ここで, dirは平面波の方向である.

オプションにて, 出力振幅と位相オフセットを指定できる. デフォルト値は上記の通り.

Uniform

Source

Uniformはすべての振動子に同じ位相/振幅を設定する.

use autd3::prelude::*;
fn main() {
let _ = 
Uniform {
    intensity: EmitIntensity::MAX, 
    phase: Phase::ZERO,
};
}
#include<autd3.hpp>
#include <limits>
int main() {
using namespace autd3;
Uniform(std::numeric_limits<EmitIntensity>::max(), Phase::zero());
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Gain;
new Uniform(
    intensity: EmitIntensity.Max,
    phase: Phase.Zero
);
from pyautd3 import EmitIntensity, Phase, Uniform
Uniform(intensity=EmitIntensity.MAX, phase=Phase.ZERO)

Custom

Source

Customはユーザーが自由に音場を生成するためのGainである.

use autd3::gain::Custom;
use autd3::prelude::*;

fn main() {
let _ =
Custom::new(|_dev| {
    |_tr| Drive {
        phase: Phase::ZERO,
        intensity: EmitIntensity::MIN,
    }
});
}
#include<autd3.hpp>
#include <autd3/gain/custom.hpp>

int main() {
using namespace autd3;
gain::Custom([](const auto& dev) {
  return [](const auto& tr) {
    return Drive(Phase::zero(), std::numeric_limits<EmitIntensity>::min());
  };
});
return 0; }
using System;
using AUTD3Sharp;
using AUTD3Sharp.Gain;
using static AUTD3Sharp.Units;

new Custom(dev => tr => new Drive(Phase.Zero, EmitIntensity.Min));
from pyautd3 import Drive, EmitIntensity, Phase
from pyautd3.gain import Custom

Custom(lambda _dev: lambda _tr: Drive(phase=Phase.ZERO, intensity=EmitIntensity.MIN))

Customコンストラクタの引数はFn(&Device) -> Fn(&Transducer) -> Driveである.

Group

Source

Groupは振動子ごとに別々のGainを使用するためのGainである.

NOTE: デバイスごとの分割で良いのであれば, Controller::group_sendの使用を推奨する.

Groupでは, 振動子に対してキーを割り当て, その各キーにGainを紐付けて使用する.

use autd3::gain::IntoBoxedGain;
use autd3::prelude::*;
use std::collections::HashMap;

fn main() -> Result<(), Box<dyn std::error::Error>> {
let x = 0.;
let y = 0.;
let z = 0.;
let _ =
Group {
    key_map: |_dev| {
        |tr| match tr.idx() {
            0..=100 => Some("null"),
            _ => Some("focus"),
        }
    },
    gain_map: HashMap::from([
        ("null", Null {}.into_boxed()),
        (
            "focus",
            Focus {
                pos: Point3::new(x, y, z),
                option: Default::default(),
            }
            .into_boxed(),
        ),
    ]),
};
Ok(())
}
#include<optional>
#include<autd3.hpp>
int main() {
using namespace autd3;
const auto x = 0.0;
const auto y = 0.0;
const auto z = 0.0;
Group(
    [](const auto& dev) {
      return [](const auto& tr) -> std::optional<const char*> {
        if (tr.idx() <= 100) return "null";
        return "focus";
      };
    },
    std::unordered_map<const char*, std::shared_ptr<Gain>>{
        {"focus", std::make_shared<Focus>(Point3(x, y, z), FocusOption{})},
        {"null", std::make_shared<Null>()}});
return 0; }
using AUTD3Sharp.Utils;
using AUTD3Sharp.Gain;
using AUTD3Sharp.Driver.Datagram;
var x = 0.0f;
var y = 0.0f;
var z = 0.0f;
new Group(
    keyMap: dev => tr => tr.Idx() <= 100 ? "null" : "focus",
    gainMap: new Dictionary<object, IGain> {
        { "null", new Null() },
        { "focus", new Focus(pos: new Point3(x, y, z), option: new FocusOption()) }
    }
);
from pyautd3 import Focus, FocusOption, Group, Null
x = 1.0
y = 0.0
z = 0.0
Group(
    key_map=lambda _: lambda tr: "null" if tr.idx() <= 100 else "focus",
    gain_map={"null": Null(), "focus": Focus(pos=[x, y, z], option=FocusOption())},
)

上の場合は, ローカルインデックスがからの振動子はNullを, それ以外の振動子はFocusを出力する.

NOTE: このサンプルでは, キーとして文字列を使用しているが, HashMapのキーとして使用できるものなら何でも良い.

Holo

Holoは多焦点を生成するためのGainである.

Install

cargo add autd3-gain-holo
target_link_libraries(<TARGET> PRIVATE autd3::gain::holo)

メインライブラリに含まれている.

メインライブラリに含まれている.

メインライブラリに含まれている.

APIs

多焦点を生成するアルゴリズムが幾つか提案されており, SDKには以下のアルゴリズムが実装されている.

  • Naive - 単一焦点解の重ね合わせ
  • GS - Gershberg-Saxon
  • GSPAT - Gershberg-Saxon for Phased Arrays of Transducers
  • LM - Levenberg-Marquardt
  • Greedy - Greedy algorithm and Brute-force search

また, 各手法は計算Backendを選べるようになっている. (GreedyのみBackendの指定はない.) SDKには以下のBackendが用意されている

  • NalgebraBackend - Nalgebraを使用
  • CUDABackend - CUDAを使用, GPUで実行 (Rust版のみ)
  • ArrayFireBackend - ArrayFireを使用 (Rust版のみ)

NOTE: CUDABackendArrayFireBackendは高速化を目的としているが, ほとんどの場合, NalgebraBackendで十分である. 使用時は, 必ずベンチマークを取ること.

振幅制約

各アルゴリズムの計算結果の振幅は最終的に振動子が出力できる範囲に制限する必要がある. これはオプションのEmissionConstraintで制御でき, 以下の4つのいずれかを指定する必要がある.

  • Normalize: 振幅の最大値ですべての振動子の振幅を割り, 規格化する.
  • Uniform: すべての振動子の振幅を指定した値にする.
  • Clamp: 振幅を指定の範囲にクランプする.
  • Multiply: 規格化後, 所定の値を乗算する.

Naive

Source

単一焦点解の重ね合わせによる多焦点Gain.

use autd3::prelude::*;
use autd3_gain_holo::{EmissionConstraint, NalgebraBackend, Pa, Naive, NaiveOption};
use std::sync::Arc;

fn main() {
let x1 = 0.;
let y1 = 0.;
let z1 = 0.;
let x2 = 0.;
let y2 = 0.;
let z2 = 0.;
let backend = Arc::new(NalgebraBackend::default());
let _ = 
Naive {
    foci: vec![
        (Point3::new(x1, y1, z1), 5e3 * Pa),
        (Point3::new(x2, y2, z2), 5e3 * Pa),
    ],
    option: NaiveOption {
        constraint: EmissionConstraint::Clamp(EmitIntensity::MIN, EmitIntensity::MAX),
        ..Default::default()
    },
    backend,
};
}
#include<autd3.hpp>
#include "autd3/gain/holo.hpp"

using namespace autd3;
using gain::holo::Pa;

int main() {
const auto x1 = 0.0;
const auto y1 = 0.0;
const auto z1 = 0.0;
const auto x2 = 0.0;
const auto y2 = 0.0;
const auto z2 = 0.0;
const auto backend = std::make_shared<gain::holo::NalgebraBackend>();
auto g = gain::holo::Naive(
    std::vector<std::pair<Point3, gain::holo::Amplitude>>{
        {Point3(x1, y1, z1), 5e3 * Pa},
        {Point3(x2, y2, z2), 5e3 * Pa},
    },
    gain::holo::NaiveOption{
        .constraint = gain::holo::EmissionConstraint::Clamp(
            std::numeric_limits<EmitIntensity>::min(),
            std::numeric_limits<EmitIntensity>::max()),
    },
    backend);
return 0; }
using AUTD3Sharp.Gain.Holo;

using AUTD3Sharp;
using AUTD3Sharp.Utils;
using static AUTD3Sharp.Units;
var x1 = 0.0f;
var y1 = 0.0f;
var z1 = 0.0f;
var x2 = 0.0f;
var y2 = 0.0f;
var z2 = 0.0f;
var backend = new NalgebraBackend();
new Naive(
    foci: [
             (new Point3(x1, y1, z1), 5e3f * Pa),
             (new Point3(x2, y2, z2), 5e3f * Pa)
    ],
    option: new NaiveOption
    {
        EmissionConstraint = EmissionConstraint.Clamp(EmitIntensity.Min, EmitIntensity.Max),
    },
    backend: backend
);
import numpy as np
from pyautd3 import EmitIntensity
from pyautd3.gain.holo import Naive, EmissionConstraint, NaiveOption, NalgebraBackend, Pa

x1 = 0.0
y1 = 0.0
z1 = 0.0
x2 = 0.0
y2 = 0.0
z2 = 0.0
backend = NalgebraBackend()
Naive(
    foci=[(np.array([x1, y1, z1]), 5e3 * Pa), (np.array([x2, y2, z2]), 5e3 * Pa)],
    option=NaiveOption(
        constraint=EmissionConstraint.Clamp(EmitIntensity.MIN, EmitIntensity.MAX),
    ),
    backend=backend,
)

GS

Source

Gershberg-Saxon, Marzoらの論文1に基づく多焦点Gain.

use autd3::prelude::*;
use autd3_gain_holo::{EmissionConstraint, NalgebraBackend, Pa, GS, GSOption};
use std::num::NonZeroUsize;
use std::sync::Arc;

fn main() {
let x1 = 0.;
let y1 = 0.;
let z1 = 0.;
let x2 = 0.;
let y2 = 0.;
let z2 = 0.;
let backend = Arc::new(NalgebraBackend::default());
let _ = 
GS {
    foci: vec![
        (Point3::new(x1, y1, z1), 5e3 * Pa),
        (Point3::new(x2, y2, z2), 5e3 * Pa),
    ],
    option: GSOption {
        repeat: NonZeroUsize::new(100).unwrap(),
        constraint: EmissionConstraint::Clamp(EmitIntensity::MIN, EmitIntensity::MAX),
        ..Default::default()
    },
    backend,
};
}
#include<autd3.hpp>
#include "autd3/gain/holo.hpp"

using namespace autd3;
using gain::holo::Pa;

int main() {
const auto x1 = 0.0;
const auto y1 = 0.0;
const auto z1 = 0.0;
const auto x2 = 0.0;
const auto y2 = 0.0;
const auto z2 = 0.0;
const auto backend = std::make_shared<gain::holo::NalgebraBackend>();
auto g = gain::holo::GS(
    std::vector<std::pair<Point3, gain::holo::Amplitude>>{
        {Point3(x1, y1, z1), 5e3 * Pa},
        {Point3(x2, y2, z2), 5e3 * Pa},
    },
    gain::holo::GSOption{
        .repeat = 100,
        .constraint = gain::holo::EmissionConstraint::Clamp(
            std::numeric_limits<EmitIntensity>::min(),
            std::numeric_limits<EmitIntensity>::max()),
    },
    backend);
return 0; }
using AUTD3Sharp.Gain.Holo;

using AUTD3Sharp;
using AUTD3Sharp.Utils;
using static AUTD3Sharp.Units;
var x1 = 0.0f;
var y1 = 0.0f;
var z1 = 0.0f;
var x2 = 0.0f;
var y2 = 0.0f;
var z2 = 0.0f;
var backend = new NalgebraBackend();
new GS(
    foci: [
             (new Point3(x1, y1, z1), 5e3f * Pa),
             (new Point3(x2, y2, z2), 5e3f * Pa)
    ],
    option: new GSOption
    {
        Repeat = 100,
        EmissionConstraint = EmissionConstraint.Clamp(EmitIntensity.Min, EmitIntensity.Max),
    },
    backend: backend
);
import numpy as np
from pyautd3 import EmitIntensity
from pyautd3.gain.holo import GS, EmissionConstraint, GSOption, NalgebraBackend, Pa

x1 = 0.0
y1 = 0.0
z1 = 0.0
x2 = 0.0
y2 = 0.0
z2 = 0.0
backend = NalgebraBackend()
GS(
    foci=[(np.array([x1, y1, z1]), 5e3 * Pa), (np.array([x2, y2, z2]), 5e3 * Pa)],
    option=GSOption(
        repeat=100,
        constraint=EmissionConstraint.Clamp(EmitIntensity.MIN, EmitIntensity.MAX),
    ),
    backend=backend,
)

repeatは反復回数, デフォルトは上記の通り. パラメータの詳細は論文1を参照されたい.

1

Marzo, Asier, and Bruce W. Drinkwater. “Holographic acoustic tweezers.” Proceedings of the National Academy of Sciences 116.1 (2019): 84-89.

GSPAT

Source

Gershberg-Saxon for Phased Arrays of Transducers, Plasenciaらの論文1に基づく多焦点Gain.

use autd3::prelude::*;
use autd3_gain_holo::{EmissionConstraint, NalgebraBackend, Pa, GSPAT, GSPATOption};
use std::num::NonZeroUsize;
use std::sync::Arc;

fn main() {
let x1 = 0.;
let y1 = 0.;
let z1 = 0.;
let x2 = 0.;
let y2 = 0.;
let z2 = 0.;
let backend = Arc::new(NalgebraBackend::default());
let _ = 
GSPAT {
    foci: vec![
        (Point3::new(x1, y1, z1), 5e3 * Pa),
        (Point3::new(x2, y2, z2), 5e3 * Pa),
    ],
    option: GSPATOption {
        repeat: NonZeroUsize::new(100).unwrap(),
        constraint: EmissionConstraint::Clamp(EmitIntensity::MIN, EmitIntensity::MAX),
        ..Default::default()
    },
    backend,
};
}
#include<autd3.hpp>
#include "autd3/gain/holo.hpp"

using namespace autd3;
using gain::holo::Pa;

int main() {
const auto x1 = 0.0;
const auto y1 = 0.0;
const auto z1 = 0.0;
const auto x2 = 0.0;
const auto y2 = 0.0;
const auto z2 = 0.0;
const auto backend = std::make_shared<gain::holo::NalgebraBackend>();
auto g = gain::holo::GSPAT(
    std::vector<std::pair<Point3, gain::holo::Amplitude>>{
        {Point3(x1, y1, z1), 5e3 * Pa},
        {Point3(x2, y2, z2), 5e3 * Pa},
    },
    gain::holo::GSPATOption{
        .repeat = 100,
        .constraint = gain::holo::EmissionConstraint::Clamp(
            std::numeric_limits<EmitIntensity>::min(),
            std::numeric_limits<EmitIntensity>::max()),
    },
    backend);
return 0; }
using AUTD3Sharp.Gain.Holo;

using AUTD3Sharp;
using AUTD3Sharp.Utils;
using static AUTD3Sharp.Units;
var x1 = 0.0f;
var y1 = 0.0f;
var z1 = 0.0f;
var x2 = 0.0f;
var y2 = 0.0f;
var z2 = 0.0f;
var backend = new NalgebraBackend();
new GSPAT(
    foci: [
             (new Point3(x1, y1, z1), 5e3f * Pa),
             (new Point3(x2, y2, z2), 5e3f * Pa)
    ],
    option: new GSPATOption
    {
        Repeat = 100,
        EmissionConstraint = EmissionConstraint.Clamp(EmitIntensity.Min, EmitIntensity.Max),
    },
    backend: backend
);
import numpy as np
from pyautd3 import EmitIntensity
from pyautd3.gain.holo import GSPAT, EmissionConstraint, GSPATOption, NalgebraBackend, Pa

x1 = 0.0
y1 = 0.0
z1 = 0.0
x2 = 0.0
y2 = 0.0
z2 = 0.0
backend = NalgebraBackend()
GSPAT(
    foci=[(np.array([x1, y1, z1]), 5e3 * Pa), (np.array([x2, y2, z2]), 5e3 * Pa)],
    option=GSPATOption(
        repeat=100,
        constraint=EmissionConstraint.Clamp(EmitIntensity.MIN, EmitIntensity.MAX),
    ),
    backend=backend,
)

repeatは反復回数, デフォルトは上記の通り. パタメータの詳細は論文1を参照されたい.

1

Plasencia, Diego Martinez, et al. “GS-PAT: high-speed multi-point sound-fields for phased arrays of transducers.” ACM Transactions on Graphics (TOG) 39.4 (2020): 138-1.

LM

Source

Levenberg-Marquardt法 (LM法) に基づく多焦点Gain. LM法はLevenberg1とMarquardt2で提案された非線形最小二乗問題の最適化法, 実装はMadsenのテキスト3に基づく.

use autd3::prelude::*;
use autd3_gain_holo::{EmissionConstraint, NalgebraBackend, Pa, LM, LMOption};
use std::num::NonZeroUsize;
use std::sync::Arc;

fn main() {
let x1 = 0.;
let y1 = 0.;
let z1 = 0.;
let x2 = 0.;
let y2 = 0.;
let z2 = 0.;
let backend = Arc::new(NalgebraBackend::default());
let _ = 
LM {
    foci: vec![
        (Point3::new(x1, y1, z1), 5e3 * Pa),
        (Point3::new(x2, y2, z2), 5e3 * Pa),
    ],
    option: LMOption {
        eps_1: 1e-8,
        eps_2: 1e-8,
        tau: 1e-3,
        k_max: NonZeroUsize::new(5).unwrap(),
        initial: vec![],
        constraint: EmissionConstraint::Clamp(EmitIntensity::MIN, EmitIntensity::MAX),
        ..Default::default()
    },
    backend,
};
}
#include<autd3.hpp>
#include "autd3/gain/holo.hpp"

using namespace autd3;
using gain::holo::Pa;

int main() {
const auto x1 = 0.0;
const auto y1 = 0.0;
const auto z1 = 0.0;
const auto x2 = 0.0;
const auto y2 = 0.0;
const auto z2 = 0.0;
const auto backend = std::make_shared<gain::holo::NalgebraBackend>();
auto g = gain::holo::LM(
    std::vector<std::pair<Point3, gain::holo::Amplitude>>{
        {Point3(x1, y1, z1), 5e3 * Pa},
        {Point3(x2, y2, z2), 5e3 * Pa},
    },
    gain::holo::LMOption{
        .eps_1 = 1e-8,
        .eps_2 = 1e-8,
        .tau = 1e-3,
        .k_max = 5,
        .initial = {},
        .constraint = gain::holo::EmissionConstraint::Clamp(
            std::numeric_limits<EmitIntensity>::min(),
            std::numeric_limits<EmitIntensity>::max()),
    },
    backend);
return 0; }
using AUTD3Sharp.Gain.Holo;

using AUTD3Sharp;
using AUTD3Sharp.Utils;
using static AUTD3Sharp.Units;
var x1 = 0.0f;
var y1 = 0.0f;
var z1 = 0.0f;
var x2 = 0.0f;
var y2 = 0.0f;
var z2 = 0.0f;
var backend = new NalgebraBackend();
new LM(
    foci: [
             (new Point3(x1, y1, z1), 5e3f * Pa),
             (new Point3(x2, y2, z2), 5e3f * Pa)
    ],
    option: new LMOption
    {
        Eps1 = 1e-8f,
        Eps2 = 1e-8f,
        Tau = 1e-3f,
        KMax = 5,
        Initial = [],
        EmissionConstraint = EmissionConstraint.Clamp(EmitIntensity.Min, EmitIntensity.Max),
    },
    backend: backend
);
import numpy as np
from pyautd3 import EmitIntensity
from pyautd3.gain.holo import LM, EmissionConstraint, LMOption, NalgebraBackend, Pa

x1 = 0.0
y1 = 0.0
z1 = 0.0
x2 = 0.0
y2 = 0.0
z2 = 0.0
backend = NalgebraBackend()
LM(
    foci=[(np.array([x1, y1, z1]), 5e3 * Pa), (np.array([x2, y2, z2]), 5e3 * Pa)],
    option=LMOption(
        eps_1=1e-8,
        eps_2=1e-8,
        tau=1e-3,
        k_max=5,
        initial = None,
        constraint=EmissionConstraint.Clamp(EmitIntensity.MIN, EmitIntensity.MAX),
    ),
    backend=backend,
)

各パラメータのデフォルトは上記の通り. パラメータの詳細はテキスト3を参照されたい.

1

Levenberg, Kenneth. “A method for the solution of certain non-linear problems in least squares.” Quarterly of applied mathematics 2.2 (1944): 164-168.

2

Marquardt, Donald W. “An algorithm for least-squares estimation of nonlinear parameters.” Journal of the society for Industrial and Applied Mathematics 11.2 (1963): 431-441.

3

Madsen, Kaj, Hans Bruun Nielsen, and Ole Tingleff. “Methods for non-linear least squares problems.” (2004).

Greedy

Source

Greedy Algorithm with Brute-Force Search, 鈴木らの論文1に基づく多焦点Gain.

use autd3::prelude::*;
use autd3_gain_holo::{EmissionConstraint, Pa, Greedy, GreedyOption, Sphere};
use std::num::NonZeroU8;

fn main() {
let x1 = 0.;
let y1 = 0.;
let z1 = 0.;
let x2 = 0.;
let y2 = 0.;
let z2 = 0.;
let _ = 
Greedy::<Sphere> {
    foci: vec![
        (Point3::new(x1, y1, z1), 5e3 * Pa),
        (Point3::new(x2, y2, z2), 5e3 * Pa),
    ],
    option: GreedyOption {
        phase_div: NonZeroU8::new(16).unwrap(),
        constraint: EmissionConstraint::Uniform(EmitIntensity::MAX),
        ..Default::default()
    },
};
}
#include <autd3.hpp>
#include "autd3/gain/holo.hpp"

using namespace autd3;
using gain::holo::Pa;

int main() {
const auto x1 = 0.0;
const auto y1 = 0.0;
const auto z1 = 0.0;
const auto x2 = 0.0;
const auto y2 = 0.0;
const auto z2 = 0.0;
auto g = gain::holo::Greedy(
    std::vector<std::pair<Point3, gain::holo::Amplitude>>{
        {Point3(x1, y1, z1), 5e3 * Pa},
        {Point3(x2, y2, z2), 5e3 * Pa},
    },
    gain::holo::GreedyOption{
        .phase_div = 16,
        .constraint = gain::holo::EmissionConstraint::Uniform(
            std::numeric_limits<EmitIntensity>::max())});
  return 0;
}
using AUTD3Sharp.Gain.Holo;

using AUTD3Sharp;
using AUTD3Sharp.Utils;
using static AUTD3Sharp.Units;
var x1 = 0.0f;
var y1 = 0.0f;
var z1 = 0.0f;
var x2 = 0.0f;
var y2 = 0.0f;
var z2 = 0.0f;
new Greedy(
    foci: [
             (new Point3(x1, y1, z1), 5e3f * Pa),
             (new Point3(x2, y2, z2), 5e3f * Pa)
    ],
    option: new GreedyOption
    {
        PhaseDiv = 16,
        EmissionConstraint = EmissionConstraint.Uniform(EmitIntensity.Max),
    }
);
import numpy as np
from pyautd3 import EmitIntensity
from pyautd3.gain.holo import Greedy, EmissionConstraint, GreedyOption, Pa

x1 = 0.0
y1 = 0.0
z1 = 0.0
x2 = 0.0
y2 = 0.0
z2 = 0.0
Greedy(
    foci=[(np.array([x1, y1, z1]), 5e3 * Pa), (np.array([x2, y2, z2]), 5e3 * Pa)],
    option=GreedyOption(
        phase_div=16,
        constraint=EmissionConstraint.Uniform(EmitIntensity.MAX),
    ),
)

phase_divは位相の離散化深度, デフォルトは上記の通り. パラメータの詳細は論文1を参照されたい.

1

Suzuki, Shun, et al. “Radiation Pressure Field Reconstruction for Ultrasound Midair Haptics by Greedy Algorithm with Brute-Force Search.” IEEE Transactions on Haptics (2021).

Cache

Source

CacheによってGainの計算結果をキャッシュするGainを生成できる.

use autd3::prelude::*;
use autd3::gain::Cache;

fn main() {
let _ = 
Cache::new(Null::new());
}
#include<autd3.hpp>
#include <autd3/gain/cache.hpp>

int main() {
using namespace autd3;
gain::Cache(Null{});
return 0; }
using AUTD3Sharp.Gain;

new Cache(new Null());
from pyautd3 import Null
from pyautd3.gain import Cache

Cache(Null())

NOTE: 当然ながら, Cacheは計算処理が重いGainに対してのみ有効なため, 実際にはNullをキャッシュする意味はない. 使用時は必ずベンチマークを取ること.

Spatio-Temporal Modulation/時空間変調

SDKでは, 音場を周期的に切り替えるための機能 (Spatio-Temporal Modulation, STM) が用意されている. SDKには単一焦点音場から8焦点音場までをサポートするFociSTMと, 任意のGainをサポートするGainSTMが用意されており, これらを送信すると音場が周期的に切り替わる.

FociSTMGainSTMはAUTD3ハードウェア上のタイマを使用するので時間精度が高いが, 制約も多い.

FociSTM/GainSTMの共通API

サンプリング設定の取得

sampling_configでサンプリング設定を取得できる.

use autd3::prelude::*;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let stm = FociSTM {
    foci: vec![Point3::origin(), Point3::origin()],
    config: 1.0 * Hz,
};
dbg!(stm.sampling_config()?.freq()?); // -> 2Hz
Ok(())
}
#include<iostream>
#include<autd3.hpp>
int main() {
using namespace autd3;
FociSTM stm(
    std::vector{
        Point3::origin(),
        Point3::origin(),
    },
    1.0f * Hz);
std::cout << stm.sampling_config().freq() << std::endl;  // -> 2Hz
                                                         return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Utils;
using static AUTD3Sharp.Units;
var stm = new FociSTM(foci: [Point3.Origin, Point3.Origin], config: 1.0f * Hz);
Console.WriteLine(stm.SamplingConfig().Freq()); // -> 2 Hz
import numpy as np
from pyautd3 import FociSTM, Hz
stm = FociSTM(
    foci=[np.array([0.0, 0.0, 0.0]), np.array([0.0, 0.0, 0.0])],
    config=1.0 * Hz,
)
print(stm.sampling_config().freq())  # -> 2Hz

LoopBehavior

FociSTM/GainSTMでは, ループの挙動を制御できる. デフォルトは無限ループである.

詳細はSegment/LoopBehaviorを参照.

ユーティリティ

Rust版のみ直線と円の軌跡を生成するユーティリティが用意されている.

use autd3::prelude::*;
fn main() {
let start = Point3::origin();
let end = Point3::origin();
let center = Point3::origin();
let radius = 30.0 * mm;
let num_points = 50;
let n = Vector3::z_axis();
let intensity = EmitIntensity::MAX;
let _ = 
FociSTM {
    foci: Line {
        start,
        end,
        num_points,
        intensity,
    },
    config: 1.0 * Hz,
};

let _ = 
FociSTM {
    foci: Circle {
        center,
        radius,
        num_points,
        n, // normal vector to the plane where the circle is drawn
        intensity,
    },
    config: 1.0 * Hz,
};
}

FociSTM

  • 使用可能な最大焦点数は
    • 拡張モードの場合は
  • サンプリングレートはで, は0より大きい16-bit符号なし整数である

FociSTMの使用方法は以下のようになる. これは, アレイの中心から直上の点を中心とした半径の円周上で焦点を回すサンプルである. 円周上を200点サンプリングし, 一周をで回るようにしている. (すなわち, サンプリング周波数はである.)

use autd3::prelude::*;
fn main() {
let center = Point3::new(0., 0., 150.0 * mm);
let point_num = 200;
let radius = 30.0 * mm;
let _ = 
FociSTM {
    foci: (0..point_num)
        .map(|i| {
            let theta = 2.0 * PI * i as f32 / point_num as f32;
            let p = radius * Vector3::new(theta.cos(), theta.sin(), 0.0);
            center + p
        })
        .collect::<Vec<_>>(),
    config: 1.0 * Hz,
};
}
#include <ranges>
#include<autd3.hpp>
#include<autd3/link/nop.hpp>
using namespace std::ranges::views;
int main() {
using namespace autd3;
const Point3 center(0, 0, 150);
const auto points_num = 200;
const auto radius = 30.0f;
std::vector<Point3> foci;
std::ranges::copy(iota(0) | take(points_num) | transform([&](auto i) {
                    const auto theta = 2.0f * pi * static_cast<float>(i) /
                                       static_cast<float>(points_num);
                    Point3 p = center + radius * Vector3(std::cos(theta),
                                                         std::sin(theta), 0);
                    return p;
                  }),
                  std::back_inserter(foci));
FociSTM(foci, 1.0f * Hz);
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Utils;
using AUTD3Sharp.Link;
using static AUTD3Sharp.Units;
var center = new Point3(0, 0, 150);
const int pointNum = 200;
const float radius = 30.0f;
new FociSTM(
    foci: Enumerable.Range(0, pointNum).Select(i =>
    {
        var theta = 2.0f * MathF.PI * i / pointNum;
        return center + radius * new Vector3(MathF.Cos(theta), MathF.Sin(theta), 0);
    }),
    config: 1.0f * Hz
);
import numpy as np
from pyautd3 import FociSTM, Hz
center = np.array([0.0, 0.0, 150.0])
point_num = 200
radius = 30.0
FociSTM(
    foci=(
        center + radius * np.array([np.cos(theta), np.sin(theta), 0])
        for theta in (2.0 * np.pi * i / point_num for i in range(point_num))
    ),
    config=1.0 * Hz,
)

configには周波数のほか, 周期やサンプリング設定を指定することができる.

サンプリング点数とサンプリング周期に関する制約によって, 指定した周波数で出力できない可能性がある. 例えば, 上記の例は200点をで回すため, サンプリング周波数はとすれば良い. しかし, 例えばpoint_num=199にすると, サンプリング周波数をにしなければならないが, を満たすような整数は存在せずエラーになる.

FociSTM::into_nearestを使用すると, 最も近いが選択されるようになるが, 指定した周波数と実際の周波数がずれる可能性があるため注意が必要である.

多焦点

FociSTMでは最大8焦点を同時に出すことができる.

以下は2焦点の例である.

use autd3::prelude::*;
fn main() {
let center = Point3::new(0., 0., 150.0 * mm);
let point_num = 200;
let radius = 30.0 * mm;
let _ = 
FociSTM {
    foci: (0..point_num)
        .map(|i| {
            let theta = 2.0 * PI * i as f32 / point_num as f32;
            let p = radius * Vector3::new(theta.cos(), theta.sin(), 0.0);
            ControlPoints {
                points: [
                    ControlPoint {
                        point: center + p,
                        phase_offset: Phase::ZERO,
                    },
                    ControlPoint {
                        point: center - p,
                        phase_offset: Phase::ZERO,
                    },
                ],
                intensity: EmitIntensity::MAX,
            }
        })
        .collect::<Vec<_>>(),
    config: 1.0 * Hz,
};
}
#include <ranges>
#include<autd3.hpp>
#include<autd3/link/nop.hpp>
using namespace std::ranges::views;
int main() {
using namespace autd3;
const Point3 center(0, 0, 150);
const auto points_num = 200;
const auto radius = 30.0f;
std::vector<ControlPoints<2>> foci;
std::ranges::copy(
    iota(0) | take(points_num) | transform([&](auto i) {
      const auto theta =
          2.0f * pi * static_cast<float>(i) / static_cast<float>(points_num);
      Vector3 p = radius * Vector3(std::cos(theta), std::sin(theta), 0);
      return ControlPoints<2>{
          .points = std::array{ControlPoint{.point = center + p,
                                            .phase_offset = Phase::zero()},
                               ControlPoint{.point = center - p,
                                            .phase_offset = Phase::zero()}},
          .intensity = std::numeric_limits<EmitIntensity>::max()};
    }),
    std::back_inserter(foci));
FociSTM stm(foci, 1.0f * Hz);
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Utils;
using AUTD3Sharp.Link;
using static AUTD3Sharp.Units;
var center = new Point3(0, 0, 150);
const int pointNum = 200;
const float radius = 30.0f;
new FociSTM(
    foci: Enumerable.Range(0, pointNum).Select(i =>
        {
            var theta = 2.0f * MathF.PI * i / pointNum;
            var p = radius * new Vector3(MathF.Cos(theta), MathF.Sin(theta), 0);
            return new ControlPoints(
                points: [
                    new ControlPoint { Point = center + p, PhaseOffset = Phase.Zero},
                    new ControlPoint { Point = center - p, PhaseOffset = Phase.Zero}
                ],
                intensity: EmitIntensity.Max
            );
        }),
    config: 1.0f * Hz
);
import numpy as np
from pyautd3 import ControlPoint, ControlPoints, EmitIntensity, FociSTM, Hz, Phase
center = np.array([0.0, 0.0, 150.0])
point_num = 200
radius = 30.0
FociSTM(
    foci=(
        ControlPoints(
            points=[
                ControlPoint(
                    point=center + radius * np.array([np.cos(theta), np.sin(theta), 0]),
                    phase_offset=Phase.ZERO,
                ),
                ControlPoint(
                    point=center - radius * np.array([np.cos(theta), np.sin(theta), 0]),
                    phase_offset=Phase.ZERO,
                ),
            ],
            intensity=EmitIntensity.MAX,
        )
        for theta in (2.0 * np.pi * i / point_num for i in range(point_num))
    ),
    config=1.0 * Hz,
)

FociSTMの多焦点音場は単純な単焦点音場の重ね合わせである. すなわち, 振動子の位置, 各焦点位置, 超音波周波数, 音速に対して, 以下の計算により位相を求めている. ここで, は各焦点の位相オフセットである. 振幅に関しては, ではなく, ソフトウェアからの指定値を使用する.

制約

データ量を削減するため, FociSTMでは, 焦点位置座標をを単位とする符号あり固定小数点数にエンコードして使用する. そのため, 各軸方向に対して, すべての振動子からの範囲にある焦点しか出力できない.

また, 内部計算も固定小数点数で行っているため, gain::Focusなどとは異なる位相になる可能性がある. 詳しくは, ファームウェアのドキュメントを参照.

GainSTM

GainSTMFociSTMとは異なり, 任意のGainを扱える. ただし, 使用できるGainの個数は (拡張モードの場合) となる.

GainSTMの使用方法は以下のようになる. これは, アレイの中心から直上の点を中心とした半径の円周上で焦点を回すサンプルである. 円周上を200点サンプリングし, 一周をで回るようにしている. (すなわち, サンプリング周波数はである.)

use autd3::prelude::*;
fn main() {
let center = Point3::new(0., 0., 150.0 * mm);
let point_num = 200;
let radius = 30.0 * mm;
let _ = 
GainSTM {
    gains: (0..point_num)
        .map(|i| {
            let theta = 2.0 * PI * i as f32 / point_num as f32;
            let p = radius * Vector3::new(theta.cos(), theta.sin(), 0.0);
            Focus {
                pos: center + p,
                option: FocusOption::default(),
            }
        })
        .collect::<Vec<_>>(),
    config: 1.0 * Hz,
    option: GainSTMOption {
        mode: GainSTMMode::PhaseIntensityFull,
    },
};
}
#include <ranges>
#include<autd3.hpp>
#include<autd3/link/nop.hpp>
using namespace std::ranges::views;
int main() {
using namespace autd3;
const Point3 center(0, 0, 150);
const auto points_num = 200;
const auto radius = 30.0f;
std::vector<Focus> gains;
std::ranges::copy(iota(0) | take(points_num) | transform([&](auto i) {
                    const auto theta = 2.0f * pi * static_cast<float>(i) /
                                       static_cast<float>(points_num);
                    return Focus(center + radius * Vector3(std::cos(theta),
                                                           std::sin(theta), 0),
                                 FocusOption{});
                  }),
                  std::back_inserter(gains));
GainSTM(gains, 1.0f * Hz,
        GainSTMOption{.mode = GainSTMMode::PhaseIntensityFull});
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Utils;
using AUTD3Sharp.Gain;
using AUTD3Sharp.Link;
using static AUTD3Sharp.Units;
var center = new Point3(0, 0, 150);
const int pointNum = 200;
const float radius = 30.0f;
new GainSTM(
    gains: Enumerable.Range(0, pointNum).Select(i =>
    {
        var theta = 2.0f * MathF.PI * i / pointNum;
        return new Focus(
            pos: center + radius * new Vector3(MathF.Cos(theta), MathF.Sin(theta), 0),
            option: new FocusOption()
        );
    }),
    config: 1.0f * Hz,
    option: new GainSTMOption
    {
        Mode = GainSTMMode.PhaseIntensityFull
    }
);
import numpy as np
from pyautd3 import Focus, FocusOption, GainSTM, GainSTMMode, GainSTMOption, Hz
center = np.array([0.0, 0.0, 150.0])
point_num = 200
radius = 30.0
GainSTM(
    gains=(
        Focus(
            pos=center + radius * np.array([np.cos(theta), np.sin(theta), 0]),
            option=FocusOption(),
        )
        for theta in (2.0 * np.pi * i / point_num for i in range(point_num))
    ),
    config=1.0 * Hz,
    option=GainSTMOption(
        mode=GainSTMMode.PhaseIntensityFull,
    ),
)

GainSTMMode

GainSTMは位相/振幅データをすべて送信するため, レイテンシが大きい1. この問題に対処するため, GainSTMには位相のみを送信して送信にかかる時間を半分にするPhaseFullモードと, 位相を4bitに圧縮して送信時間を4分の1にするPhaseHalfモードが用意されている. この2つのモードでは振幅は最大値が使用される.

デフォルトは振幅/位相データを送るPhaseIntensityFullモードである.

1

FociSTM<1>のおよそ75倍のレイテンシ

Modulation

ModulationはAM変調を制御するための構造体の総称であり, これを送信することで超音波にAM変調をかけられる.

Modulationは音圧振幅に掛け合わされる. 例えば, Sine変調を印加した場合の音圧振幅は以下のようになり, 音圧振幅の正の部分 (或いは, 負の部分) の包絡線がのsin波に従う.

なお, 現在, Modulationには以下の制約がある.

  • バッファサイズは最大で65536
    • 拡張モードを使用すると131072
  • サンプリングレートはである. ここで, は0より大きい符号なし整数である.

SDKにはデフォルトでいくつかの種類のAMを生成するためのModulationが用意されている.

  • Static - 変調なし
  • Sine - 正弦波
    • Fourier - 正弦波の重ね合わせ
  • Square - 矩形波
  • Wav - Wavファイルをもとにした変調
  • Csv - Csvファイルをもとにした変調
  • Custom - ユーザー定義の変調
  • Cache - 他のModulationの計算結果をキャッシュする
  • RadiationPressure - 放射圧に対して変調を適用する
  • Fir - Firフィルタを適用する

Modulationの共通API

Sampling設定

Modulationのサンプリング設定はsampling_configで取得できる.

use autd3::prelude::*;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let m = Sine {
    freq: 150 * Hz,
    option: SineOption::default(),
};
dbg!(m.sampling_config().freq()?); // -> 4kHz
    Ok(())
}
#include<iostream>
#include<autd3.hpp>
#include<autd3/link/nop.hpp>
int main() {
using namespace autd3;
const auto m = Sine(150 * Hz, SineOption{});
std::cout << m.sampling_config().freq() << std::endl;  // -> 4kHz
                                                       return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Modulation;
using static AUTD3Sharp.Units;
var m = new Sine(freq: 150u * Hz, option: new SineOption());
Console.WriteLine(m.SamplingConfig().Freq()); // -> 4kHz
from pyautd3 import Hz
from pyautd3.modulation import Sine, SineOption
m = Sine(freq=150 * Hz, option=SineOption())
print(m.sampling_config().freq())  # -> 4kHz

また, 一部のModulationはオプションでサンプリング設定を変更できる.

サンプリング設定についての詳細はサンプリング設定についてを参照されたい.

LoopBehavior

Modulationはループの挙動を制御できる. デフォルトは無限ループである.

詳細はSegment/LoopBehaviorを参照.

Static

Source

変調なし.

use autd3::prelude::*;
fn main() {
let _ = 
Static { intensity: 0xFF };
}
#include<autd3.hpp>
int main() {
using namespace autd3;
Static(0xFF);
return 0; }
using AUTD3Sharp.Modulation;
new Static(intensity: 0xFF);
from pyautd3 import Static
Static(intensity=0xFF)

intensityにて振幅を指定できる. デフォルトは上記の通り.

Sine

Source

音圧をSin波状に変形するためのModulation.

use autd3::prelude::*;
fn main() {
let _ = 
Sine {
    freq: 150 * Hz,
    option: SineOption {
        intensity: u8::MAX,
        offset: 0x80,
        phase: 0. * rad,
        clamp: false,
        sampling_config: SamplingConfig::FREQ_4K,
    },
};
}
#include<autd3.hpp>
int main() {
using namespace autd3;
Sine(150 * Hz, SineOption{
                   .intensity = 0xFF,
                   .offset = 0x80,
                   .phase = 0.0f * rad,
                   .clamp = false,
                   .sampling_config = SamplingConfig::freq_4k(),
               });
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Modulation;
using static AUTD3Sharp.Units;
new Sine(
    freq: 150u * Hz,
    option: new SineOption
    {
        Intensity = 0xFF,
        Offset = 0x80,
        Phase = 0f * rad,
        Clamp = false,
        SamplingConfig = SamplingConfig.Freq4K
    }
);
from pyautd3 import Hz, SamplingConfig, Sine, SineOption, rad
Sine(
    freq=150 * Hz,
    option=SineOption(
        intensity=0xFF,
        offset=0x80,
        phase=0.0 * rad,
        clamp=False,
        sampling_config=SamplingConfig.FREQ_4K,
    ),
)

Sineは音圧の波形が となるようなAMをかける. ここでは床関数を表す.

clampfalseだと, 上記の式においての範囲外の値になるようなintensity, offsetが指定された場合にエラーを返す. エラーを返すのではなく, 範囲外の値をにクランプする場合は, clamptrueを指定する.

これらの値のデフォルトは上記の通りである.

周波数制約

Sineはデフォルトだと周波数に厳格であり, サンプリング周波数によって出力不可能な周波数が指定された場合にはエラーを返す.

その場合はサンプリング設定を変更するか, into_nearestを使用することで, 出力可能な周波数の内で最も近い周波数で変調することができる.

use autd3::prelude::*;
fn main() {
    let _ = 
Sine {
    freq: 150. * Hz,
    option: SineOption::default(),
}
.into_nearest();
}
#include<autd3.hpp>
int main() {
using namespace autd3;
Sine(150.0f * Hz, SineOption{}).into_nearest();
return 0; }
using AUTD3Sharp.Modulation;
using static AUTD3Sharp.Units;
new Sine(freq: 150.0f * Hz, option: new SineOption()).IntoNearest();
from pyautd3 import Hz, Sine, SineOption
Sine(freq=150.0 * Hz, option=SineOption()).into_nearest()

Fourier

Source

複数の周波数の正弦波を重ね合わせた波形を生成するModulation.

use autd3::modulation::{Fourier, FourierOption};
use autd3::prelude::*;

fn main() {
let _ = 
Fourier {
    components: vec![
        Sine {
            freq: 100 * Hz,
            option: Default::default(),
        },
        Sine {
            freq: 150 * Hz,
            option: Default::default(),
        },
    ],
    option: FourierOption {
        scale_factor: None,
        clamp: false,
        offset: 0x00,
    },
};
}
#include<autd3.hpp>
#include <autd3/modulation/fourier.hpp>

int main() {
using namespace autd3;
modulation::Fourier({Sine(100 * Hz, SineOption{}),
                     Sine(150 * Hz, SineOption{})},
                    modulation::FourierOption{
                        .scale_factor = std::nullopt,
                        .clamp = false,
                        .offset = 0x00,
                    });

return 0; }
using AUTD3Sharp.Modulation;

using static AUTD3Sharp.Units;
new Fourier(
    components: [
        new Sine(freq: 100u * Hz, option: new SineOption()),
        new Sine(freq: 150u * Hz, option: new SineOption())
    ],
    option: new FourierOption
    {
        ScaleFactor = null,
        Clamp = false,
        Offset = 0x00
    }
);
from pyautd3 import Hz, Sine, SineOption
from pyautd3.modulation import Fourier, FourierOption

Fourier(
    components=[
        Sine(freq=100 * Hz, option=SineOption()),
        Sine(freq=150 * Hz, option=SineOption()),
    ],
    option=FourierOption(
        scale_factor=None,
        clamp=False,
        offset=0x00,
    ),
)

スケールファクタと値のクランプ

Fourierの計算は, 以下の式で行われる, スケールファクタが指定されていない場合, が使用される.

clampfalseだと, 上記の式においての範囲外の値になるようなintensity, offsetが指定された場合にエラーを返す. エラーを返すのではなく, 範囲外の値をにクランプする場合は, clamptrueを指定する.

これらの値のデフォルトは上記の通りである.

Square

Source

矩形波状のModulation.

use autd3::prelude::*;
fn main() {
let _ = 
Square {
    freq: 150 * Hz,
    option: SquareOption {
        low: u8::MIN,
        high: u8::MAX,
        duty: 0.5,
        sampling_config: SamplingConfig::FREQ_4K,
    },
};
}
#include<autd3.hpp>
int main() {
using namespace autd3;
Square(150 * Hz, SquareOption{
                     .low = 0x00,
                     .high = 0xFF,
                     .duty = 0.5f,
                     .sampling_config = SamplingConfig::freq_4k(),
                 });
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Modulation;
using static AUTD3Sharp.Units;
new Square(
    freq: 150u * Hz,
    option: new SquareOption
    {
        Low = 0x00,
        High = 0xFF,
        Duty = 0.5f,
        SamplingConfig = SamplingConfig.Freq4K
    }
);
from pyautd3 import Hz, SamplingConfig, Square, SquareOption
Square(
    freq=150 * Hz,
    option=SquareOption(
        low=0x00,
        high=0xFF,
        duty=0.5,
        sampling_config=SamplingConfig.FREQ_4K,
    ),
)

周波数制約

Squareはデフォルトだと周波数に厳格であり, サンプリング周波数によって出力不可能な周波数が指定された場合にはエラーを返す.

その場合はサンプリング設定を変更するか, into_nearestを使用することで, 出力可能な周波数の内で最も近い周波数で変調することができる.

use autd3::prelude::*;
fn main() {
let _ = 
Square {
    freq: 150.0 * Hz,
    option: Default::default(),
}
.into_nearest();
}
#include<autd3.hpp>
int main() {
using namespace autd3;
Square(150.0f * Hz, SquareOption{}).into_nearest();
return 0; }
using AUTD3Sharp.Modulation;
using static AUTD3Sharp.Units;
new Square(freq: 150.0f * Hz, option: new SquareOption()).IntoNearest();
from pyautd3 import Hz, Square, SquareOption
Square(freq=150.0 * Hz, option=SquareOption()).into_nearest()

Wav

Source

WavはWavファイルをもとに構成されるModulationである.

Install

cargo add autd3-modulation-audio-file
target_link_libraries(<TARGET> PRIVATE autd3::modulation::audio_file)

メインライブラリに含まれている.

メインライブラリに含まれている.

メインライブラリに含まれている.

APIs

use autd3_modulation_audio_file::Wav;

fn main() -> Result<(), Box<dyn std::error::Error>> {
let _ = 
Wav::new("path/to/foo.wav")?;
Ok(())
}
#include<autd3.hpp>
#include "autd3/modulation/audio_file.hpp"

int main() {
using namespace autd3;
modulation::audio_file::Wav("path/to/foo.wav");
return 0; }
using AUTD3Sharp.Modulation.AudioFile;

new Wav(path: "path/to/foo.wav");
import pathlib
from pyautd3.modulation.audio_file import Wav

Wav(path=pathlib.Path("path/to/foo.wav"))

Wavデータとして, モノラル, かつ, 8,16,24,32bit整数, 及び, 32bit浮動小数点数のデータ形式に対応している.

それぞれのデータ値は以下の式を通して, 8bit符号なし整数の変調データに変換される. ここでは最も近い整数を表す.

Csv

Source

CsvはCsvファイルをもとに構成されるModulationである.

Install

cargo add autd3-modulation-audio-file
target_link_libraries(<TARGET> PRIVATE autd3::modulation::audio_file)

メインライブラリに含まれている.

メインライブラリに含まれている.

メインライブラリに含まれている.

APIs

use autd3::prelude::*;
use autd3_modulation_audio_file::{Csv, CsvOption};

fn main() {
let _ = 
Csv {
    path: "path/to/foo.csv",
    sampling_config: 4000.0 * Hz,
    option: CsvOption { delimiter: b',' },
};
}
#include<autd3.hpp>
#include "autd3/modulation/audio_file.hpp"

int main() {
using namespace autd3;
const auto path = "path/to/foo.csv";
modulation::audio_file::Csv(path, 4000.0f * Hz,
                            modulation::audio_file::CsvOption{
                                .delimiter = ',',
                            });
return 0; }
using AUTD3Sharp.Modulation.AudioFile;

using static AUTD3Sharp.Units;
new Csv(
    path: "path/to/foo.csv",
    samplingConfig: 4000f * Hz,
    option: new CsvOption
    {
        Delimiter = ',',
    }
);
import pathlib
from pyautd3 import Hz
from pyautd3.modulation.audio_file import Csv, CsvOption

Csv(
    path=pathlib.Path("path/to/foo.csv"),
    sampling_config=4000.0 * Hz,
    option=CsvOption(
        delimiter=",",
    ),
)

Custom

Source

Customは指定された符号なし8bitデータ列を出力するModulationである.

use autd3::modulation::Custom;

use autd3::prelude::*;
fn main() {
let _ = 
Custom {
    buffer: vec![0xFF, 0x00],
    sampling_config: 4.0 * kHz,
};
}
#include<vector>
#include<autd3.hpp>
#include <autd3/modulation/custom.hpp>

int main() {
using namespace autd3;
modulation::Custom(std::vector<uint8_t>{0xFF, 0x00}, 4000.0f * Hz);
return 0; }
using AUTD3Sharp;
using static AUTD3Sharp.Units;
using AUTD3Sharp.Modulation;

new Custom(
    buffer: [0xFF, 0x00],
    samplingConfig: 4000f * Hz
);
from pyautd3 import Hz
import numpy as np
from pyautd3.modulation import Custom

Custom(
    buffer=np.array([0xFF, 0x00]),
    sampling_config=4000.0 * Hz,
)

Cache

Source

Cacheで計算結果をキャッシュしておくためのModulationを生成できる.

use autd3::prelude::*;
use autd3::modulation::Cache;

fn main() {
let _ = 
Cache::new(Static::default());
}
#include<autd3.hpp>
#include <autd3/modulation/cache.hpp>

int main() {
using namespace autd3;
modulation::Cache(Static{});
return 0; }
using AUTD3Sharp.Modulation;

new Cache(new Static());
from pyautd3 import Static
from pyautd3.modulation import Cache

Cache(Static())

NOTE: ほとんどのModulationに対して, キャッシュするより都度計算し直したほうが速い. 使用時は必ずベンチマークを取ること.

RadiationPressure

Source

RadiationPressureModulationを音圧ではなく, 放射圧 (音圧の二乗に比例) に印加するためのModulationである.

例えば, Sine変調にRadiationPressureを適用した場合の音圧振幅の放射圧は以下のようになり, 放射圧の包絡線がのsin波に従う.

use autd3::prelude::*;
use autd3::modulation::RadiationPressure;
 
fn main() {
let _ = 
RadiationPressure {
    target: Sine {
        freq: 150 * Hz,
        option: Default::default(),
    },
};
}
#include<autd3.hpp>
#include <autd3/modulation/radiation_pressure.hpp>

int main() {
using namespace autd3;
modulation::RadiationPressure(Sine(150 * Hz, SineOption{}));
return 0; }
using AUTD3Sharp.Modulation;
using static AUTD3Sharp.Units;

new RadiationPressure(target: new Sine(freq: 150u * Hz, option: new SineOption()));
from pyautd3 import Hz, Sine, SineOption
from pyautd3.modulation import RadiationPressure

RadiationPressure(target=Sine(freq=150 * Hz, option=SineOption()))

Fir

Source

Firでは任意のModulationFIRフィルタを適用することができる.

以下は, サンプリング周波数, タップ数, カットオフ周波数のLPFを適用する例である.

use autd3::prelude::*;
use autd3::modulation::Fir;

fn main() -> Result<(), Box<dyn std::error::Error>> {
let _ = 
Fir {
    target: Sine {
        freq: 150. * Hz,
        option: SineOption {
            sampling_config: SamplingConfig::new(20.0 * kHz),
            ..Default::default()
        },
    },
    coef: vec![
        -0.000009, -0.000013, -0.000016, -0.000021, -0.000025, -0.000030, -0.000036,
        -0.000042, -0.000049, -0.000056, -0.000064, -0.000072, -0.000080, -0.000088,
        -0.000096, -0.000105, -0.000113, -0.000120, -0.000128, -0.000134, -0.000139,
        -0.000143, -0.000146, -0.000146, -0.000145, -0.000141, -0.000135, -0.000124,
        -0.000111, -0.000093, -0.000071, -0.000044, -0.000012, 0.000026, 0.000071,
        0.000122, 0.000180, 0.000247, 0.000321, 0.000405, 0.000497, 0.000599, 0.000712,
        0.000835, 0.000968, 0.001113, 0.001270, 0.001439, 0.001619, 0.001812, 0.002018,
        0.002236, 0.002467, 0.002711, 0.002968, 0.003237, 0.003518, 0.003812, 0.004118,
        0.004435, 0.004764, 0.005103, 0.005452, 0.005811, 0.006178, 0.006554, 0.006937,
        0.007326, 0.007720, 0.008119, 0.008521, 0.008925, 0.009331, 0.009737, 0.010141,
        0.010543, 0.010941, 0.011335, 0.011722, 0.012101, 0.012472, 0.012833, 0.013183,
        0.013520, 0.013843, 0.014152, 0.014445, 0.014720, 0.014978, 0.015217, 0.015435,
        0.015633, 0.015810, 0.015964, 0.016096, 0.016204, 0.016289, 0.016350, 0.016386,
        0.016399, 0.016386, 0.016350, 0.016289, 0.016204, 0.016096, 0.015964, 0.015810,
        0.015633, 0.015435, 0.015217, 0.014978, 0.014720, 0.014445, 0.014152, 0.013843,
        0.013520, 0.013183, 0.012833, 0.012472, 0.012101, 0.011722, 0.011335, 0.010941,
        0.010543, 0.010141, 0.009737, 0.009331, 0.008925, 0.008521, 0.008119, 0.007720,
        0.007326, 0.006937, 0.006554, 0.006178, 0.005811, 0.005452, 0.005103, 0.004764,
        0.004435, 0.004118, 0.003812, 0.003518, 0.003237, 0.002968, 0.002711, 0.002467,
        0.002236, 0.002018, 0.001812, 0.001619, 0.001439, 0.001270, 0.001113, 0.000968,
        0.000835, 0.000712, 0.000599, 0.000497, 0.000405, 0.000321, 0.000247, 0.000180,
        0.000122, 0.000071, 0.000026, -0.000012, -0.000044, -0.000071, -0.000093,
        -0.000111, -0.000124, -0.000135, -0.000141, -0.000145, -0.000146, -0.000146,
        -0.000143, -0.000139, -0.000134, -0.000128, -0.000120, -0.000113, -0.000105,
        -0.000096, -0.000088, -0.000080, -0.000072, -0.000064, -0.000056, -0.000049,
        -0.000042, -0.000036, -0.000030, -0.000025, -0.000021, -0.000016, -0.000013,
        -0.000009,
    ],
};
Ok(())
}
#include<autd3.hpp>
#include <autd3/modulation/fir.hpp>

int main() {
using namespace autd3;
modulation::Fir(
    Sine(150 * Hz,
         SineOption{
             .sampling_config = SamplingConfig(20.0f * kHz),
         }),
    std::vector{
        -0.000009f, -0.000013f, -0.000016f, -0.000021f, -0.000025f, -0.000030f,
        -0.000036f, -0.000042f, -0.000049f, -0.000056f, -0.000064f, -0.000072f,
        -0.000080f, -0.000088f, -0.000096f, -0.000105f, -0.000113f, -0.000120f,
        -0.000128f, -0.000134f, -0.000139f, -0.000143f, -0.000146f, -0.000146f,
        -0.000145f, -0.000141f, -0.000135f, -0.000124f, -0.000111f, -0.000093f,
        -0.000071f, -0.000044f, -0.000012f, 0.000026f,  0.000071f,  0.000122f,
        0.000180f,  0.000247f,  0.000321f,  0.000405f,  0.000497f,  0.000599f,
        0.000712f,  0.000835f,  0.000968f,  0.001113f,  0.001270f,  0.001439f,
        0.001619f,  0.001812f,  0.002018f,  0.002236f,  0.002467f,  0.002711f,
        0.002968f,  0.003237f,  0.003518f,  0.003812f,  0.004118f,  0.004435f,
        0.004764f,  0.005103f,  0.005452f,  0.005811f,  0.006178f,  0.006554f,
        0.006937f,  0.007326f,  0.007720f,  0.008119f,  0.008521f,  0.008925f,
        0.009331f,  0.009737f,  0.010141f,  0.010543f,  0.010941f,  0.011335f,
        0.011722f,  0.012101f,  0.012472f,  0.012833f,  0.013183f,  0.013520f,
        0.013843f,  0.014152f,  0.014445f,  0.014720f,  0.014978f,  0.015217f,
        0.015435f,  0.015633f,  0.015810f,  0.015964f,  0.016096f,  0.016204f,
        0.016289f,  0.016350f,  0.016386f,  0.016399f,  0.016386f,  0.016350f,
        0.016289f,  0.016204f,  0.016096f,  0.015964f,  0.015810f,  0.015633f,
        0.015435f,  0.015217f,  0.014978f,  0.014720f,  0.014445f,  0.014152f,
        0.013843f,  0.013520f,  0.013183f,  0.012833f,  0.012472f,  0.012101f,
        0.011722f,  0.011335f,  0.010941f,  0.010543f,  0.010141f,  0.009737f,
        0.009331f,  0.008925f,  0.008521f,  0.008119f,  0.007720f,  0.007326f,
        0.006937f,  0.006554f,  0.006178f,  0.005811f,  0.005452f,  0.005103f,
        0.004764f,  0.004435f,  0.004118f,  0.003812f,  0.003518f,  0.003237f,
        0.002968f,  0.002711f,  0.002467f,  0.002236f,  0.002018f,  0.001812f,
        0.001619f,  0.001439f,  0.001270f,  0.001113f,  0.000968f,  0.000835f,
        0.000712f,  0.000599f,  0.000497f,  0.000405f,  0.000321f,  0.000247f,
        0.000180f,  0.000122f,  0.000071f,  0.000026f,  -0.000012f, -0.000044f,
        -0.000071f, -0.000093f, -0.000111f, -0.000124f, -0.000135f, -0.000141f,
        -0.000145f, -0.000146f, -0.000146f, -0.000143f, -0.000139f, -0.000134f,
        -0.000128f, -0.000120f, -0.000113f, -0.000105f, -0.000096f, -0.000088f,
        -0.000080f, -0.000072f, -0.000064f, -0.000056f, -0.000049f, -0.000042f,
        -0.000036f, -0.000030f, -0.000025f, -0.000021f, -0.000016f, -0.000013f,
        -0.000009f});
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Modulation;
using static AUTD3Sharp.Units;

new Fir(
    target: new Sine(freq: 150u * Hz, option: new SineOption
    {
        SamplingConfig = new SamplingConfig(20f * kHz)
    }),
    coef: [-0.000009f, -0.000013f, -0.000016f, -0.000021f, -0.000025f,
            -0.000030f, -0.000036f, -0.000042f, -0.000049f, -0.000056f,
            -0.000064f, -0.000072f, -0.000080f, -0.000088f, -0.000096f,
            -0.000105f, -0.000113f, -0.000120f, -0.000128f, -0.000134f,
            -0.000139f, -0.000143f, -0.000146f, -0.000146f, -0.000145f,
            -0.000141f, -0.000135f, -0.000124f, -0.000111f, -0.000093f,
            -0.000071f, -0.000044f, -0.000012f, 0.000026f,  0.000071f,
            0.000122f,  0.000180f,  0.000247f,  0.000321f,  0.000405f,
            0.000497f,  0.000599f,  0.000712f,  0.000835f,  0.000968f,
            0.001113f,  0.001270f,  0.001439f,  0.001619f,  0.001812f,
            0.002018f,  0.002236f,  0.002467f,  0.002711f,  0.002968f,
            0.003237f,  0.003518f,  0.003812f,  0.004118f,  0.004435f,
            0.004764f,  0.005103f,  0.005452f,  0.005811f,  0.006178f,
            0.006554f,  0.006937f,  0.007326f,  0.007720f,  0.008119f,
            0.008521f,  0.008925f,  0.009331f,  0.009737f,  0.010141f,
            0.010543f,  0.010941f,  0.011335f,  0.011722f,  0.012101f,
            0.012472f,  0.012833f,  0.013183f,  0.013520f,  0.013843f,
            0.014152f,  0.014445f,  0.014720f,  0.014978f,  0.015217f,
            0.015435f,  0.015633f,  0.015810f,  0.015964f,  0.016096f,
            0.016204f,  0.016289f,  0.016350f,  0.016386f,  0.016399f,
            0.016386f,  0.016350f,  0.016289f,  0.016204f,  0.016096f,
            0.015964f,  0.015810f,  0.015633f,  0.015435f,  0.015217f,
            0.014978f,  0.014720f,  0.014445f,  0.014152f,  0.013843f,
            0.013520f,  0.013183f,  0.012833f,  0.012472f,  0.012101f,
            0.011722f,  0.011335f,  0.010941f,  0.010543f,  0.010141f,
            0.009737f,  0.009331f,  0.008925f,  0.008521f,  0.008119f,
            0.007720f,  0.007326f,  0.006937f,  0.006554f,  0.006178f,
            0.005811f,  0.005452f,  0.005103f,  0.004764f,  0.004435f,
            0.004118f,  0.003812f,  0.003518f,  0.003237f,  0.002968f,
            0.002711f,  0.002467f,  0.002236f,  0.002018f,  0.001812f,
            0.001619f,  0.001439f,  0.001270f,  0.001113f,  0.000968f,
            0.000835f,  0.000712f,  0.000599f,  0.000497f,  0.000405f,
            0.000321f,  0.000247f,  0.000180f,  0.000122f,  0.000071f,
            0.000026f,  -0.000012f, -0.000044f, -0.000071f, -0.000093f,
            -0.000111f, -0.000124f, -0.000135f, -0.000141f, -0.000145f,
            -0.000146f, -0.000146f, -0.000143f, -0.000139f, -0.000134f,
            -0.000128f, -0.000120f, -0.000113f, -0.000105f, -0.000096f,
            -0.000088f, -0.000080f, -0.000072f, -0.000064f, -0.000056f,
            -0.000049f, -0.000042f, -0.000036f, -0.000030f, -0.000025f,
            -0.000021f, -0.000016f, -0.000013f, -0.000009f]
);
from pyautd3 import Hz, Sine, SineOption, kHz, SamplingConfig
from pyautd3.modulation import Fir

Fir(
    target=Sine(
        freq=150 * Hz,
        option=SineOption(
            sampling_config=SamplingConfig(20.0 * kHz),
        ),
    ),
    coef=[
        -0.000009, -0.000013, -0.000016, -0.000021, -0.000025, -0.000030, -0.000036,
        -0.000042, -0.000049, -0.000056, -0.000064, -0.000072, -0.000080, -0.000088,
        -0.000096, -0.000105, -0.000113, -0.000120, -0.000128, -0.000134, -0.000139,
        -0.000143, -0.000146, -0.000146, -0.000145, -0.000141, -0.000135, -0.000124,
        -0.000111, -0.000093, -0.000071, -0.000044, -0.000012, 0.000026, 0.000071,
        0.000122, 0.000180, 0.000247, 0.000321, 0.000405, 0.000497, 0.000599, 0.000712,
        0.000835, 0.000968, 0.001113, 0.001270, 0.001439, 0.001619, 0.001812, 0.002018,
        0.002236, 0.002467, 0.002711, 0.002968, 0.003237, 0.003518, 0.003812, 0.004118,
        0.004435, 0.004764, 0.005103, 0.005452, 0.005811, 0.006178, 0.006554, 0.006937,
        0.007326, 0.007720, 0.008119, 0.008521, 0.008925, 0.009331, 0.009737, 0.010141,
        0.010543, 0.010941, 0.011335, 0.011722, 0.012101, 0.012472, 0.012833, 0.013183,
        0.013520, 0.013843, 0.014152, 0.014445, 0.014720, 0.014978, 0.015217, 0.015435,
        0.015633, 0.015810, 0.015964, 0.016096, 0.016204, 0.016289, 0.016350, 0.016386,
        0.016399, 0.016386, 0.016350, 0.016289, 0.016204, 0.016096, 0.015964, 0.015810,
        0.015633, 0.015435, 0.015217, 0.014978, 0.014720, 0.014445, 0.014152, 0.013843,
        0.013520, 0.013183, 0.012833, 0.012472, 0.012101, 0.011722, 0.011335, 0.010941,
        0.010543, 0.010141, 0.009737, 0.009331, 0.008925, 0.008521, 0.008119, 0.007720,
        0.007326, 0.006937, 0.006554, 0.006178, 0.005811, 0.005452, 0.005103, 0.004764,
        0.004435, 0.004118, 0.003812, 0.003518, 0.003237, 0.002968, 0.002711, 0.002467,
        0.002236, 0.002018, 0.001812, 0.001619, 0.001439, 0.001270, 0.001113, 0.000968,
        0.000835, 0.000712, 0.000599, 0.000497, 0.000405, 0.000321, 0.000247, 0.000180,
        0.000122, 0.000071, 0.000026, -0.000012, -0.000044, -0.000071, -0.000093,
        -0.000111, -0.000124, -0.000135, -0.000141, -0.000145, -0.000146, -0.000146,
        -0.000143, -0.000139, -0.000134, -0.000128, -0.000120, -0.000113, -0.000105,
        -0.000096, -0.000088, -0.000080, -0.000072, -0.000064, -0.000056, -0.000049,
        -0.000042, -0.000036, -0.000030, -0.000025, -0.000021, -0.000016, -0.000013,
        -0.000009,
    ],
)

サンプリング設定について

Modulation, FociSTM/GainSTMのサンプリング設定について説明する.

サンプリング周波数はで, より大きい16-bit符号なし整数である.

また, Silencerの設定によって指定できるサンプリング周波数の最大値が決まる. 詳しくはSilencerを参照.

サンプリング設定のコンストラクタには, 分周比, または, サンプリング周波数, サンプリング周期を指定する.

use std::num::NonZeroU16;
use std::time::Duration;
use autd3::prelude::*;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let _ = 
SamplingConfig::new(NonZeroU16::new(10).unwrap());
// or
let _ = 
SamplingConfig::new(4000.0 * Hz);
// or
let _ = 
SamplingConfig::new(Duration::from_micros(250));
Ok(())
}
#include<chrono>
#include<autd3.hpp>
#include<autd3/link/nop.hpp>
int main() {
using namespace autd3;
SamplingConfig(10);
// or
SamplingConfig(4000.0f * Hz);
// or
SamplingConfig(std::chrono::microseconds(250));
return 0; }
using AUTD3Sharp;
using static AUTD3Sharp.Units;
new SamplingConfig(10);
// or
new SamplingConfig(4000f * Hz);
// or
new SamplingConfig(Duration.FromMicros(250));
from pyautd3 import Duration, Hz, SamplingConfig
SamplingConfig(10)
# or
SamplingConfig(4000.0 * Hz)
# or
SamplingConfig(Duration.from_micros(250))

サンプリング周波数制限の緩和

使用は推奨されないが, 出力可能な周波数/周期の内で最も指定した値に近い周波数/周期を使用する方法もある.

use std::time::Duration;
use autd3::prelude::*;
fn main() {
let _ = 
SamplingConfig::new(4000.0 * Hz).into_nearest();
// or
let _ = 
SamplingConfig::new(Duration::from_micros(250)).into_nearest();
}
#include<autd3.hpp>
#include<chrono>
int main() {
using namespace autd3;
const auto s =
SamplingConfig(4000.0f * Hz).into_nearest();
// or
const auto sp =
SamplingConfig(std::chrono::microseconds(250)).into_nearest();
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Modulation;
using static AUTD3Sharp.Units;
new SamplingConfig(4000.0f * Hz).IntoNearest();
// or
new SamplingConfig(Duration.FromMicros(250)).IntoNearest();
from pyautd3 import Duration, Hz, SamplingConfig
SamplingConfig(4000.0 * Hz).into_nearest()
# or
SamplingConfig(Duration.from_micros(250)).into_nearest()

Segment/LoopBehavior

Segment

Modulation, Gain, FociSTM, GainSTMのデータ領域にはそれぞれ, Segmentが2つ用意されている.

特に指定しない限りは, Segment::S0が使用される.

データを書き込むSegmentを変更する場合は, WithSegmentを送信する.

use autd3::prelude::*;
fn main() {
let _ = 
WithSegment {
    inner: Static::default(),
    segment: Segment::S1,
    transition_mode: Some(TransitionMode::Immediate),
};
}
#include<autd3.hpp>
int main() {
using namespace autd3;
WithSegment(Static{}, Segment::S1, TransitionMode::Immediate());
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Modulation;
new WithSegment(
    inner: new Static(),
    segment: Segment.S1,
    transitionMode: TransitionMode.Immediate
);
from pyautd3 import Segment, Static, TransitionMode, WithSegment
WithSegment(
    inner=Static(),
    segment=Segment.S1,
    transition_mode=TransitionMode.Immediate,
)

transition_modeには, Segmentの切り替え条件を指定する.

  • 遷移先セグメントが無限ループ時にのみ使用可能

    • Immediate : 直ちに切り替える
    • Ext : 直ちに切り替え, 拡張モードにする (各Segmentのデータを出力後, 自動でSegmentを切り替えるモード)
  • 遷移先セグメントが有限ループ時にのみ使用可能

    • SyncIdx : 遷移先のSegmentのデータインデックスがになったときに切り替える
    • SysTime(DcSysTime) : 指定した時刻になったときに切り替える
    • GPIO(GPIOIn) : 指定したGPIOピンに信号が入力されたときに切り替える

NOTE: GainImmediateのみサポートしている.

遷移先のループの挙動を指定する場合は, LoopBehaviorを参照されたい.

データの書き込みのみを行い, Segmentを切り替えたくない場合はtransition_modeNoneを指定する.

Segmentの切り替え

Segmentを切り替えたいだけの場合は, SwapSegmentを送信する.

use autd3::prelude::*;
fn main() {
SwapSegment::Modulation(Segment::S1, TransitionMode::Immediate);
}
#include<autd3.hpp>
#include<autd3/link/nop.hpp>
int main() {
using namespace autd3;
SwapSegment::Modulation(Segment::S1, TransitionMode::Immediate());
return 0; }
using AUTD3Sharp;
SwapSegment.Modulation(Segment.S1, TransitionMode.Immediate);
from pyautd3 import Segment, SwapSegment, TransitionMode
SwapSegment.Modulation(Segment.S1, TransitionMode.Immediate)

LoopBehavior

ModulationFociSTM, GainSTMWithLoopBehaviorを送信することでループの挙動を制御できる.

ループ挙動の指定は, セグメントを切り替えたときにのみ有効であることに注意.

use std::num::NonZeroU16;
use autd3::prelude::*;
fn main() {
let _ = 
WithLoopBehavior {
    inner: Static::default(),
    loop_behavior: LoopBehavior::Infinite,
    segment: Segment::S1,
    transition_mode: Some(TransitionMode::Immediate),
};

let _ = 
WithLoopBehavior {
    inner: Static::default(),
    loop_behavior: LoopBehavior::Finite(NonZeroU16::new(1).unwrap()),
    segment: Segment::S1,
    transition_mode: Some(TransitionMode::SyncIdx),
};
}
#include<autd3.hpp>
int main() {
using namespace autd3;

WithLoopBehavior(Static{}, LoopBehavior::Infinite(), Segment::S1,
                 TransitionMode::Immediate());

WithLoopBehavior(Static{}, LoopBehavior::Finite(1), Segment::S1,
                 TransitionMode::SyncIdx());
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Modulation;
new WithLoopBehavior(
    inner: new Static(),
    loopBehavior: LoopBehavior.Infinite,
    segment: Segment.S1,
    transitionMode: TransitionMode.Immediate
);

new WithLoopBehavior(
    inner: new Static(),
    loopBehavior: LoopBehavior.Finite(1),
    segment: Segment.S1,
    transitionMode: TransitionMode.SyncIdx
);
from pyautd3 import Segment, Static, TransitionMode, LoopBehavior, WithLoopBehavior
WithLoopBehavior(
    inner=Static(),
    loop_behavior=LoopBehavior.Infinite,
    segment=Segment.S1,
    transition_mode=TransitionMode.Immediate,
)

WithLoopBehavior(
    inner=Static(),
    loop_behavior=LoopBehavior.Finite(1),
    segment=Segment.S1,
    transition_mode=TransitionMode.SyncIdx,
)

Silencer

AUTD3には出力を静音化するためのSilencerが用意されている. Silencerは, 振動子の駆動信号の急激な変動を抑制し, 静音化する.

理論

詳細は鈴木らの論文1を参照されたい.

大まかに概要を述べると,

  • 振幅変調された超音波は可聴音を生じる
  • 超音波振動子を駆動する際に, 位相変化が振幅変動を引き起こす
    • したがって, 可聴音の騒音が生じる
  • 位相変化を線形に補間し, 段階的に変化させることで振幅変動を抑えられる
    • したがって, 騒音を低減できる
  • 補間を細かくやると, その分だけ騒音を小さくできる

となる.

Silencerの設定

Silencerの設定にはSilencerを送信する.

Silencerはデフォルトで適当な値に設定されている. Silencerを無効化する場合は, disableを送信する.

use autd3::prelude::*;
fn main() {
let _ = 
Silencer::default();
let _ = 
Silencer::disable();
}
#include<autd3.hpp>
int main() {
using namespace autd3;
Silencer();
Silencer::disable();
return 0; }
using AUTD3Sharp;
new Silencer();
Silencer.Disable();
from pyautd3 import Silencer
Silencer()
Silencer.disable()

より細く設定する場合は, 以下2つのモードから選択する必要がある.

なお, デフォルトではFixed completion time modeに設定されている.

Fixed update rate mode

Fixed update rate modeにおける位相の変化

Silencerは位相の変化を線形補間し, 段階的にすることで静音化を行う. 即ち, 位相の時系列データを(単純)移動平均フィルタに通しているのにほとんど等しい. ただし, 位相データが周期的であるという事を考慮している点で異なる.

例えば, 超音波の周期の場合を考える. 即ち, , に対応する. ここで, 時刻で, 位相がからに変化したとする. この時, Silencerによる位相変化は以下の図のようになる.

位相の変化

一方, 時刻で, 位相がからに変化したとする. この時のSilencerによる位相変化は以下の図のようになる. これは, よりも, のほうが近いためである.

位相の変化 (位相変化量がより大きい場合)

つまり, Silencerは現在のと目標値に対して として位相を更新する. ここで, は1ステップ当たりの更新量 (Silencerstep) を表す. なお, 更新周波数はとなっている.

が小さいほど, 位相変化はなだらかになり騒音が抑制される.

による変化の違い

この実装の都合上, 移動平均フィルタとは異なる挙動を示す場合がある. 一つは, 上に示した位相変化量がより大きい場合であり, もう一つが, 途中でもう一度位相が変化する場合である. この時の位相変化の例を以下に示す. 元時系列に対する忠実度という観点では移動平均フィルタが正しいが, 位相変化量がより大きい場合を考慮したり, を可変にする (即ち, フィルタ長を可変にする) のが大変なため現在のような実装となっている.

移動平均フィルタとの比較

Fixed update rate modeにおける振幅の変化

振幅変動が騒音を引き起こすので, 振幅パラメータも同等のフィルタをかけることでAMによる騒音を抑制できる.

振幅パラメータは位相とは異なり周期的ではないので, 現在のと目標値に対して のように更新する.

Fixed update rate modeの設定

Fixed update rate modeを設定するには, 以下のようにする. 引数はそれぞれ, 上述のに対応する (単位は).

use autd3::prelude::*;
use std::num::NonZeroU16;
fn main() {
let _ = 
Silencer {
    config: FixedUpdateRate {
        intensity: NonZeroU16::MIN,
        phase: NonZeroU16::MIN,
    },
};
}
#include<autd3.hpp>
int main() {
using namespace autd3;
Silencer{FixedUpdateRate{.intensity = 1, .phase = 1}};
return 0; }
using AUTD3Sharp;
new Silencer(
    config: new FixedUpdateRate
    {
        Intensity = 1,
        Phase = 1
    }
);
from pyautd3 import Silencer, FixedUpdateRate
Silencer(
    config=FixedUpdateRate(
        intensity=1,
        phase=1,
    ),
)

Fixed completion time mode

このモードでは, 位相/振幅変化が一定の時間で完了するようになる.

Fixed completion time modeの設定

Fixed completion time modeを設定するには, 以下のようにする.

intensity, phaseはそれぞれ, 振幅/位相変化の完了まで時間に対応する. これらは超音波周期 () の整数倍である必要がある.

use autd3::prelude::*;
use std::time::Duration;
fn main() {
let _ = 
Silencer {
    config: FixedCompletionTime {
        intensity: Duration::from_micros(250),
        phase: Duration::from_micros(250),
        strict_mode: true,
    },
};
}
#include<chrono>
#include<autd3.hpp>
int main() {
using namespace autd3;
Silencer{FixedCompletionTime{.intensity = std::chrono::microseconds(250),
                             .phase = std::chrono::microseconds(250),
                             .strict_mode = true}};
return 0; }
using AUTD3Sharp;
new Silencer(
    config: new FixedCompletionTime
    {
        Intensity = Duration.FromMicros(250),
        Phase = Duration.FromMicros(250),
        StrictMode = true
    }
);
from pyautd3 import Duration, FixedCompletionTime, Silencer
Silencer(
    config=FixedCompletionTime(
        intensity=Duration.from_micros(250),
        phase=Duration.from_micros(250),
        strict_mode=True,
    ),
)

デフォルト値は, 位相変化が, 振幅変化がである. なお, Silencerの無効化は, 位相/振幅変化が超音波周期 () で終わることと等価である.

なお, このモードでは, ModulationFociSTM, GainSTMの位相/振幅がSilencerに指定した時間で完了できない場合にエラーが返される. すなわち, 以下の条件が満たされる必要がある.

  • Silencerの振幅変化完了時間 Modulationのサンプリング周期
  • Silencerの振幅変化完了時間 FociSTM/GainSTMのサンプリング周期
  • Silencerの位相変化完了時間 FociSTM/GainSTMのサンプリング周期

strict_modefalseにすれば, この条件を満たさない場合でもエラーを返さないようになるが, 推奨はされない.

1

Suzuki, Shun, et al. “Reducing amplitude fluctuation by gradual phase shift in midair ultrasound haptics.” IEEE transactions on haptics 13.1 (2020): 87-93.

位相補正

PhaseCorrectionを送信することで, 位相を補正することができる.

use autd3::prelude::*;
fn main() {
let _ =
PhaseCorrection::new(|_dev| |_tr| Phase::ZERO);
}
#include<chrono>
#include<autd3.hpp>
#include<autd3/link/nop.hpp>
int main() {
using namespace autd3;
PhaseCorrection([](const auto&) {
  return [](const auto&) { return Phase::zero(); };
});
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Utils;
new PhaseCorrection(_dev => _tr => Phase.Zero);
from pyautd3 import Phase, PhaseCorrection
PhaseCorrection(lambda _dev: lambda _tr: Phase.ZERO)

PhaseCorrectionコンストラクタの引数はFn(&Device) -> Fn(&Transducer) -> Phaseで, 振動子毎に指定した位相値がGain, FociSTM, GainSTMの値に加算されるようになる.

強度とパルス幅

PWM信号のDuty比と超音波出力の間には非線形な関係がある. この関係を補正するために, PulseWidthEncoderを使用できる.

ファームウェア内部には, Gain/FociSTM/GainSTMの強度 (EmitIntensity) 値 () とModulationの振幅データ () をかけ合わせたものをで割った強度値 () をインデックスにして, PWM信号のパルス幅 () を決定するテーブルがある. PulseWidthEncoderを送信することで, このテーブルを変更できる. なお, PWM信号の周期は512である.

デフォルトでは, 強度値と超音波の出力が (理論上) 線形になるように, となるテーブルが書き込まれている. ここでは最も近い整数を表す.

例えば, 以下のようにすると, 強度値とパルス幅の関係が線形になる (すなわち, 強度値と超音波出力は非線形になる).

use autd3::prelude::*;
fn main() {
let _ =
PulseWidthEncoder::new(|_dev| |i| PulseWidth::from_duty(i.0 as f32 / 510.0).unwrap());
}
#include<autd3.hpp>
#include<autd3/link/nop.hpp>
#include<vector>
int main() {
using namespace autd3;
auto autd =
Controller::open({AUTD3{}}, link::Nop{});
PulseWidthEncoder([](const auto& dev) {
  return [](const auto i) {
    return PulseWidth::from_duty(static_cast<float>(i.value()) / 510.0f);
  };
});
return 0; }
using AUTD3Sharp;
new PulseWidthEncoder(_dev => i => PulseWidth.FromDuty((float)i.Item1 / 510.0f));
from pyautd3 import PulseWidthEncoder, PulseWidth
PulseWidthEncoder(lambda _dev: lambda i: PulseWidth.from_duty(i.value / 510.0))

コンストラクタの引数は各デバイスに対して, テーブルのインデックスを引数にパルス幅を返す関数を返す関数Fn(&Device) -> Fn(EmitIntensity) -> PulseWidthである.

GPIOOutputs

GPIOOutputsを送信することで, GPIOピンの出力を各デバイス・ピン毎に設定できる.

GPIOピン
use autd3::prelude::*;
fn main() {
let _ =
GPIOOutputs::new(|_dev, gpio| {
    if gpio == GPIOOut::O0 {
        GPIOOutputType::BaseSignal
    } else {
        GPIOOutputType::None
    }
});
}
#include<autd3.hpp>
int main() {
using namespace autd3;
GPIOOutputs([](const auto& dev, const auto& gpio) {
  return gpio == GPIOOut::O0 ? GPIOOutputType::BaseSignal
                             : GPIOOutputType::None;
});
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Utils;
new GPIOOutputs(
    (dev, gpio) => gpio == GPIOOut.O0 ? GPIOOutputType.BaseSignal : GPIOOutputType.None
);
from pyautd3 import GPIOOutputs, GPIOOutputType, GPIOOut
GPIOOutputs(
    lambda _dev, gpio: (
        GPIOOutputType.BaseSignal if gpio == GPIOOut.O0 else GPIOOutputType.NONE
    ),
)

出力可能なデータは以下の通り.

  • None: 出力しない
  • BaseSignal: 基準信号 (超音波と同じ周波数のDuty比50%矩形波)
  • Thermo: 温度センサーがアサートされているかどうか
  • ForceFan: ForceFanフラグがアサートされているかどうか
  • Sync: EtherCAT同期信号
  • ModSegment: Modulationのセグメント
  • ModIdx(u16): Modulationのインデックスが指定した値のときにHighになる
  • StmSegment: STMのセグメント
  • StmIdx(u16): STMのインデックスが指定した値のときにHighになる
  • IsStmMode: FociSTM/GainSTMが使用されているかどうか
  • PwmOut(&Transducer): 指定した振動子のPWM出力
  • SysTimeEq(DcSysTime): 指定したシステム時間の間Highになる
  • Direct(bool): 指定した値を出力する

ファン制御

AUTD3デバイスにはファンがついており, Auto, Off, Onの3つのファンモードが有る.

Autoモードでは温度監視ICがICの温度を監視し, 一定温度以上になると自動でファンを起動する. Offモードではファンは常時オフであり, Onモードでは常時オンになる.

モードの切替は, ファン横のジャンパスイッチで行う. 少しわかりにくいが, 以下の図のようにファン側をショートするとAuto, 真ん中でOff, 右側でOnとなる.

AUTDファン制御用のジャンパスイッチ

Autoモードの場合は温度が高くなると自動的にファンが起動する.

Autoモードの場合, ForceFanを送信することでファンを強制的に起動できる.

use autd3::prelude::*;
fn main() {
let _ =
ForceFan::new(|_dev| true);
}
#include<autd3.hpp>
int main() {
using namespace autd3;
ForceFan([](const auto&) { return true; });
return 0; }
using AUTD3Sharp;
new ForceFan(_ => true);
from pyautd3 import ForceFan
ForceFan(lambda _: True)

ForceFanコンストラクタの引数はFn(&Device) -> boolで, デバイス毎にファンを強制駆動するかどうかを指定する.

NOTE: Autoモードではファンを強制的にオフにすることはできない.

AUTD3 Simulator

AUTD Simulator (以下, シミュレータ) はその名の通りAUTDのシミュレータであり, Windows/Linux/macOSで動作する.

AUTD Server

シミュレータはAUTD Serverに付属している. GitHub Releasesにてインストーラを配布しているので, これをダウンロードし, 指示に従ってインストールする.

AUTD Serverを実行すると, 以下のような画面になるので, Simulatorタブを開き, Runボタンを押すとシミュレータが起動する.

シミュレータが起動すると接続待ちの状態になる.

この状態で, Simulatorリンクを使ってControlleropenすると, シミュレータ上には, 振動子の位置に円と, 画面中央に黒いパネルが表示される.

この黒いパネルを“Slice“と呼び, この“Slice“を使って任意の位置の音場を可視化できる. また, その時, 振動子の位相が色相で, 振幅が色強度で表される.

なお, シミュレータで表示される音場はシンプルな球面波の重ね合わせであり, 指向性や非線形効果などは考慮されない.

画面左に表示されているGUIでSliceやカメラの操作が行える.

Sliceタブ

SliceタブではSliceの大きさと位置, 回転を変えられる. 回転はXYZのオイラー角で指定する. なお, 「xy」, 「yz」, 「zx」ボタンを押すと, Sliceを各平面に平行な状態へ回転させる.

また, 「Color settings」の項目ではカラーリングのパレットの変更や, Max pressureの変更ができる. 大量のデバイスを使用すると色が飽和する場合があるので, その時は「Max pressure」の値を大きくすれば良い.

Cameraタブ

Cameraタブではカメラの位置, 回転, Field of View, Near clip, Far clipの設定を変えられる. 回転はXYZのオイラー角で指定する.

Configタブ

Configタブでは音速とフォントサイズ, 及び, 背景色の設定ができる.

また, 各デバイスごとのshow/enable/overheatの設定を切り替えられる. showをOffにした場合は, 表示されないだけで音場に寄与する. enableをOffにすると音場に寄与しなくなる. また, overheatをOnにすると, 温度センサがアサートされた状態を模擬できる.

Infoタブ

Infoタブでは, FPSや各デバイス毎のSilencerやModulation, STMの情報が確認できる.

Silencerの設定は確認できるがこれは音場には反映されない.

「Mod enable」をOnにすると, Modulationが音場に反映されるようになる.

ModulationとSTMは実時間を元にどのインデックスのデータを出力するかを決めている. この時間を表すのが, 「System time」であり, 2000年1月1日0時0分0秒からの経過時間をナノ秒単位で表す.

「Auto play」をOnにすると「System time」が自動的に設定される. そうでない場合は, 手動で時間を進めることができる.

エミュレータ

Emulatorを使用すると, Simulatorよりも詳細な出力位相/パルス幅, 出力電圧, 出力音波, および, 音場の計算が行える.

NOTE: 現在, EmulatorはRust, 及び, Pythonからのみ使用可能である. C++, C#へ移植する予定はない.

Install

cargo add autd3-emulator
pip install pyautd3_emulator

APIs

Emulatorが出力するデータはPolarsのDataFrameである. 詳しくは, Polarsのドキュメントを参照されたい.

振動子テーブル

振動子の一覧を表示する.

各列には, デバイスインデックス, 振動子の(ローカル)インデックス, 位置, 極方向が格納される.

use autd3::prelude::*;
use autd3_emulator::*;

fn main() {
let emulator = Emulator::new([AUTD3::default()]);
let df = emulator.transducer_table();
dbg!(df);
}
from pyautd3 import AUTD3
from pyautd3_emulator import Emulator

with Emulator([AUTD3()]) as emulator:
    df = emulator.transducer_table()
    print(df)
┌─────────┬────────┬────────────┬────────────┬───────┬─────┬─────┬─────┐
│ dev_idx ┆ tr_idx ┆ x[mm]      ┆ y[mm]      ┆ z[mm] ┆ nx  ┆ ny  ┆ nz  │
│ ---     ┆ ---    ┆ ---        ┆ ---        ┆ ---   ┆ --- ┆ --- ┆ --- │
│ u16     ┆ u8     ┆ f32        ┆ f32        ┆ f32   ┆ f32 ┆ f32 ┆ f32 │
╞═════════╪════════╪════════════╪════════════╪═══════╪═════╪═════╪═════╡
│ 0       ┆ 0      ┆ 0.0        ┆ 0.0        ┆ 0.0   ┆ 0.0 ┆ 0.0 ┆ 1.0 │
│ 0       ┆ 1      ┆ 10.16      ┆ 0.0        ┆ 0.0   ┆ 0.0 ┆ 0.0 ┆ 1.0 │
│ 0       ┆ 2      ┆ 20.32      ┆ 0.0        ┆ 0.0   ┆ 0.0 ┆ 0.0 ┆ 1.0 │
│ 0       ┆ 3      ┆ 30.48      ┆ 0.0        ┆ 0.0   ┆ 0.0 ┆ 0.0 ┆ 1.0 │
│ 0       ┆ 4      ┆ 40.639999  ┆ 0.0        ┆ 0.0   ┆ 0.0 ┆ 0.0 ┆ 1.0 │
│ …       ┆ …      ┆ …          ┆ …          ┆ …     ┆ …   ┆ …   ┆ …   │
│ 0       ┆ 244    ┆ 132.080002 ┆ 132.080002 ┆ 0.0   ┆ 0.0 ┆ 0.0 ┆ 1.0 │
│ 0       ┆ 245    ┆ 142.23999  ┆ 132.080002 ┆ 0.0   ┆ 0.0 ┆ 0.0 ┆ 1.0 │
│ 0       ┆ 246    ┆ 152.399994 ┆ 132.080002 ┆ 0.0   ┆ 0.0 ┆ 0.0 ┆ 1.0 │
│ 0       ┆ 247    ┆ 162.559998 ┆ 132.080002 ┆ 0.0   ┆ 0.0 ┆ 0.0 ┆ 1.0 │
│ 0       ┆ 248    ┆ 172.720001 ┆ 132.080002 ┆ 0.0   ┆ 0.0 ┆ 0.0 ┆ 1.0 │
└─────────┴────────┴────────────┴────────────┴───────┴─────┴─────┴─────┘

出力位相/パルス幅の計算

use autd3::prelude::*;
use autd3_emulator::*;
use std::time::Duration;

fn main() -> Result<(), Box<dyn std::error::Error>> {
let emulator = Emulator::new([AUTD3::default()]);

let record = emulator.record(|autd| {
    autd.send(Silencer::default())?;
    autd.send((
        Sine {
            freq: 200 * Hz,
            option: Default::default(),
        },
        Uniform {
            intensity: EmitIntensity::MAX,
            phase: Phase::ZERO,
        },
    ))?;
    autd.tick(Duration::from_millis(10))?;
    Ok(())
})?;
let df = record.phase();
dbg!(df);

let df = record.pulse_width();
dbg!(df);
Ok(())
}
from pyautd3 import (
    AUTD3,
    Duration,
    EmitIntensity,
    Hz,
    Phase,
    Silencer,
    Sine,
    SineOption,
    Uniform,
)
from pyautd3_emulator import Emulator, Recorder

with Emulator([AUTD3()]) as emulator:

    def f(autd: Recorder) -> None:
        autd.send(Silencer())
        autd.send(
            (
                Sine(freq=200.0 * Hz, option=SineOption()),
                Uniform(intensity=EmitIntensity.MAX, phase=Phase.ZERO),
            ),
        )
        autd.tick(Duration.from_millis(10))

    record = emulator.record(f)

    df = record.phase()
    print(df)

    df = record.pulse_width()
    print(df)

NOTE: tickで指定する時間間隔は, の倍数である必要がある.

各時刻 (単位) における, 位相/パルス幅が各列に格納される. 各列の名前は<phase/pulse_width>@<時刻>[ns]である.

各行は振動子テーブルの行と対応している.

┌─────────────┬────────────────┬────────────────┬────────────────┬───┬────────────────┬───────────────┬───────────────┬───────────────┐
│ phase@0[ns] ┆ phase@25000[ns ┆ phase@50000[ns ┆ phase@75000[ns ┆ … ┆ phase@9900000[ ┆ phase@9925000 ┆ phase@9950000 ┆ phase@9975000 │
│ ---         ┆ ]              ┆ ]              ┆ ]              ┆   ┆ ns]            ┆ [ns]          ┆ [ns]          ┆ [ns]          │
│ u8          ┆ ---            ┆ ---            ┆ ---            ┆   ┆ ---            ┆ ---           ┆ ---           ┆ ---           │
│             ┆ u8             ┆ u8             ┆ u8             ┆   ┆ u8             ┆ u8            ┆ u8            ┆ u8            │
╞═════════════╪════════════════╪════════════════╪════════════════╪═══╪════════════════╪═══════════════╪═══════════════╪═══════════════╡
│ 0           ┆ 0              ┆ 0              ┆ 0              ┆ … ┆ 0              ┆ 0             ┆ 0             ┆ 0             │
│ 0           ┆ 0              ┆ 0              ┆ 0              ┆ … ┆ 0              ┆ 0             ┆ 0             ┆ 0             │
│ 0           ┆ 0              ┆ 0              ┆ 0              ┆ … ┆ 0              ┆ 0             ┆ 0             ┆ 0             │
│ 0           ┆ 0              ┆ 0              ┆ 0              ┆ … ┆ 0              ┆ 0             ┆ 0             ┆ 0             │
│ 0           ┆ 0              ┆ 0              ┆ 0              ┆ … ┆ 0              ┆ 0             ┆ 0             ┆ 0             │
│ …           ┆ …              ┆ …              ┆ …              ┆ … ┆ …              ┆ …             ┆ …             ┆ …             │
│ 0           ┆ 0              ┆ 0              ┆ 0              ┆ … ┆ 0              ┆ 0             ┆ 0             ┆ 0             │
│ 0           ┆ 0              ┆ 0              ┆ 0              ┆ … ┆ 0              ┆ 0             ┆ 0             ┆ 0             │
│ 0           ┆ 0              ┆ 0              ┆ 0              ┆ … ┆ 0              ┆ 0             ┆ 0             ┆ 0             │
│ 0           ┆ 0              ┆ 0              ┆ 0              ┆ … ┆ 0              ┆ 0             ┆ 0             ┆ 0             │
│ 0           ┆ 0              ┆ 0              ┆ 0              ┆ … ┆ 0              ┆ 0             ┆ 0             ┆ 0             │
└─────────────┴────────────────┴────────────────┴────────────────┴───┴────────────────┴───────────────┴───────────────┴───────────────┘
┌───────────────────┬───────────────────────┬───────────────────────┬───────────────────────┬───┬─────────────────────────┬─────────────────────────┬────────────────────────┬────────────────────────┐
│ pulse_width@0[ns] ┆ pulse_width@25000[ns] ┆ pulse_width@50000[ns] ┆ pulse_width@75000[ns] ┆ … ┆ pulse_width@9900000[ns] ┆ pulse_width@9925000[ns] ┆ pulse_width@9950000[ns ┆ pulse_width@9975000[ns │
│ ---               ┆ ---                   ┆ ---                   ┆ ---                   ┆   ┆ ---                     ┆ ---                     ┆ ]                      ┆ ]                      │
│ u16               ┆ u16                   ┆ u16                   ┆ u16                   ┆   ┆ u16                     ┆ u16                     ┆ ---                    ┆ ---                    │
│                   ┆                       ┆                       ┆                       ┆   ┆                         ┆                         ┆ u16                    ┆ u16                    │
╞═══════════════════╪═══════════════════════╪═══════════════════════╪═══════════════════════╪═══╪═════════════════════════╪═════════════════════════╪════════════════════════╪════════════════════════╡
│ 8                 ┆ 16                    ┆ 24                    ┆ 33                    ┆ … ┆ 50                      ┆ 53                      ┆ 55                     ┆ 57                     │
│ 8                 ┆ 16                    ┆ 24                    ┆ 33                    ┆ … ┆ 50                      ┆ 53                      ┆ 55                     ┆ 57                     │
│ 8                 ┆ 16                    ┆ 24                    ┆ 33                    ┆ … ┆ 50                      ┆ 53                      ┆ 55                     ┆ 57                     │
│ 8                 ┆ 16                    ┆ 24                    ┆ 33                    ┆ … ┆ 50                      ┆ 53                      ┆ 55                     ┆ 57                     │
│ 8                 ┆ 16                    ┆ 24                    ┆ 33                    ┆ … ┆ 50                      ┆ 53                      ┆ 55                     ┆ 57                     │
│ …                 ┆ …                     ┆ …                     ┆ …                     ┆ … ┆ …                       ┆ …                       ┆ …                      ┆ …                      │
│ 8                 ┆ 16                    ┆ 24                    ┆ 33                    ┆ … ┆ 50                      ┆ 53                      ┆ 55                     ┆ 57                     │
│ 8                 ┆ 16                    ┆ 24                    ┆ 33                    ┆ … ┆ 50                      ┆ 53                      ┆ 55                     ┆ 57                     │
│ 8                 ┆ 16                    ┆ 24                    ┆ 33                    ┆ … ┆ 50                      ┆ 53                      ┆ 55                     ┆ 57                     │
│ 8                 ┆ 16                    ┆ 24                    ┆ 33                    ┆ … ┆ 50                      ┆ 53                      ┆ 55                     ┆ 57                     │
│ 8                 ┆ 16                    ┆ 24                    ┆ 33                    ┆ … ┆ 50                      ┆ 53                      ┆ 55                     ┆ 57                     │
└───────────────────┴───────────────────────┴───────────────────────┴───────────────────────┴───┴─────────────────────────┴─────────────────────────┴────────────────────────┴────────────────────────┘

出力電圧

use autd3::prelude::*;
use autd3_emulator::*;
use std::time::Duration;

fn main() -> Result<(), Box<dyn std::error::Error>> {
let emulator = Emulator::new([AUTD3::default()]);

let record = emulator.record(|autd| {
    autd.send(Silencer::disable())?;
    autd.send((
        Static { intensity: 0xFF },
        Uniform {
            intensity: EmitIntensity::MAX,
            phase: Phase(0x40),
        },
    ))?;
    autd.tick(Duration::from_millis(1))?;
    Ok(())
})?;

let df = record.output_voltage();
dbg!(df);
    Ok(())
}
from pyautd3 import AUTD3, Duration, EmitIntensity, Phase, Silencer, Static, Uniform
from pyautd3_emulator import Emulator, Recorder

with Emulator([AUTD3()]) as emulator:

    def f(autd: Recorder) -> None:
        autd.send(Silencer.disable())
        autd.send((Static(), Uniform(phase=Phase(0x40), intensity=EmitIntensity.MAX)))
        autd.tick(Duration.from_millis(1))

    record = emulator.record(f)

    df = record.output_voltage()
    print(df)

各時刻 (単位) における, 出力電圧が各列に格納される. 各列の名前はvoltage[V]@<時刻>[25us/512]である.

各行は振動子テーブルの行と対応している.

┌────────────────────────┬────────────────────────┬────────────────────────┬────────────────────────┬───┬────────────────────────────┬────────────────────────────┬────────────────────────────┬────────────────────────────┐
│ voltage[V]@0[25us/512] ┆ voltage[V]@1[25us/512] ┆ voltage[V]@2[25us/512] ┆ voltage[V]@3[25us/512] ┆ … ┆ voltage[V]@20476[25us/512] ┆ voltage[V]@20477[25us/512] ┆ voltage[V]@20478[25us/512] ┆ voltage[V]@20479[25us/512] │
│ ---                    ┆ ---                    ┆ ---                    ┆ ---                    ┆   ┆ ---                        ┆ ---                        ┆ ---                        ┆ ---                        │
│ f32                    ┆ f32                    ┆ f32                    ┆ f32                    ┆   ┆ f32                        ┆ f32                        ┆ f32                        ┆ f32                        │
╞════════════════════════╪════════════════════════╪════════════════════════╪════════════════════════╪═══╪════════════════════════════╪════════════════════════════╪════════════════════════════╪════════════════════════════╡
│ 12.0                   ┆ 12.0                   ┆ 12.0                   ┆ 12.0                   ┆ … ┆ -12.0                      ┆ -12.0                      ┆ -12.0                      ┆ -12.0                      │
│ 12.0                   ┆ 12.0                   ┆ 12.0                   ┆ 12.0                   ┆ … ┆ -12.0                      ┆ -12.0                      ┆ -12.0                      ┆ -12.0                      │
│ 12.0                   ┆ 12.0                   ┆ 12.0                   ┆ 12.0                   ┆ … ┆ -12.0                      ┆ -12.0                      ┆ -12.0                      ┆ -12.0                      │
│ 12.0                   ┆ 12.0                   ┆ 12.0                   ┆ 12.0                   ┆ … ┆ -12.0                      ┆ -12.0                      ┆ -12.0                      ┆ -12.0                      │
│ 12.0                   ┆ 12.0                   ┆ 12.0                   ┆ 12.0                   ┆ … ┆ -12.0                      ┆ -12.0                      ┆ -12.0                      ┆ -12.0                      │
│ …                      ┆ …                      ┆ …                      ┆ …                      ┆ … ┆ …                          ┆ …                          ┆ …                          ┆ …                          │
│ 12.0                   ┆ 12.0                   ┆ 12.0                   ┆ 12.0                   ┆ … ┆ -12.0                      ┆ -12.0                      ┆ -12.0                      ┆ -12.0                      │
│ 12.0                   ┆ 12.0                   ┆ 12.0                   ┆ 12.0                   ┆ … ┆ -12.0                      ┆ -12.0                      ┆ -12.0                      ┆ -12.0                      │
│ 12.0                   ┆ 12.0                   ┆ 12.0                   ┆ 12.0                   ┆ … ┆ -12.0                      ┆ -12.0                      ┆ -12.0                      ┆ -12.0                      │
│ 12.0                   ┆ 12.0                   ┆ 12.0                   ┆ 12.0                   ┆ … ┆ -12.0                      ┆ -12.0                      ┆ -12.0                      ┆ -12.0                      │
│ 12.0                   ┆ 12.0                   ┆ 12.0                   ┆ 12.0                   ┆ … ┆ -12.0                      ┆ -12.0                      ┆ -12.0                      ┆ -12.0                      │
└────────────────────────┴────────────────────────┴────────────────────────┴────────────────────────┴───┴────────────────────────────┴────────────────────────────┴────────────────────────────┴────────────────────────────┘

出力音圧

use autd3::prelude::*;
use autd3_emulator::*;
use std::time::Duration;

fn main() -> Result<(), Box<dyn std::error::Error>> {
let emulator = Emulator::new([AUTD3::default()]);

let record = emulator.record(|autd| {
    autd.send(Silencer::disable())?;
    autd.send((
        Static { intensity: 0xFF },
        Uniform {
            intensity: EmitIntensity::MAX,
            phase: Phase(0x40),
        },
    ))?;
    autd.tick(Duration::from_millis(1))?;
    Ok(())
})?;

let df = record.output_ultrasound();
dbg!(df);
    Ok(())
}
from pyautd3 import AUTD3, Duration, EmitIntensity, Phase, Silencer, Static, Uniform
from pyautd3_emulator import Emulator, Recorder

with Emulator([AUTD3()]) as emulator:

    def f(autd: Recorder) -> None:
        autd.send(Silencer.disable())
        autd.send((Static(), Uniform(phase=Phase(0x40), intensity=EmitIntensity.MAX)))
        autd.tick(Duration.from_millis(1))

    record = emulator.record(f)

    df = record.output_ultrasound()
    print(df)

各時刻 (単位) における, 規格化された出力音圧が各列に格納される. 各列の名前はp[a.u.]@<時刻>[25us/512]である.

各行は振動子テーブルの行と対応している.

┌─────────────────────┬─────────────────────┬─────────────────────┬─────────────────────┬───┬─────────────────────────┬─────────────────────────┬─────────────────────────┬─────────────────────────┐
│ p[a.u.]@0[25us/512] ┆ p[a.u.]@1[25us/512] ┆ p[a.u.]@2[25us/512] ┆ p[a.u.]@3[25us/512] ┆ … ┆ p[a.u.]@20476[25us/512] ┆ p[a.u.]@20477[25us/512] ┆ p[a.u.]@20478[25us/512] ┆ p[a.u.]@20479[25us/512] │
│ ---                 ┆ ---                 ┆ ---                 ┆ ---                 ┆   ┆ ---                     ┆ ---                     ┆ ---                     ┆ ---                     │
│ f32                 ┆ f32                 ┆ f32                 ┆ f32                 ┆   ┆ f32                     ┆ f32                     ┆ f32                     ┆ f32                     │
╞═════════════════════╪═════════════════════╪═════════════════════╪═════════════════════╪═══╪═════════════════════════╪═════════════════════════╪═════════════════════════╪═════════════════════════╡
│ 0.0                 ┆ -0.000272           ┆ -0.000481           ┆ -0.000618           ┆ … ┆ -0.36185                ┆ -0.350698               ┆ -0.339501               ┆ -0.328258               │
│ 0.0                 ┆ -0.000272           ┆ -0.000481           ┆ -0.000618           ┆ … ┆ -0.36185                ┆ -0.350698               ┆ -0.339501               ┆ -0.328258               │
│ 0.0                 ┆ -0.000272           ┆ -0.000481           ┆ -0.000618           ┆ … ┆ -0.36185                ┆ -0.350698               ┆ -0.339501               ┆ -0.328258               │
│ 0.0                 ┆ -0.000272           ┆ -0.000481           ┆ -0.000618           ┆ … ┆ -0.36185                ┆ -0.350698               ┆ -0.339501               ┆ -0.328258               │
│ 0.0                 ┆ -0.000272           ┆ -0.000481           ┆ -0.000618           ┆ … ┆ -0.36185                ┆ -0.350698               ┆ -0.339501               ┆ -0.328258               │
│ …                   ┆ …                   ┆ …                   ┆ …                   ┆ … ┆ …                       ┆ …                       ┆ …                       ┆ …                       │
│ 0.0                 ┆ -0.000272           ┆ -0.000481           ┆ -0.000618           ┆ … ┆ -0.36185                ┆ -0.350698               ┆ -0.339501               ┆ -0.328258               │
│ 0.0                 ┆ -0.000272           ┆ -0.000481           ┆ -0.000618           ┆ … ┆ -0.36185                ┆ -0.350698               ┆ -0.339501               ┆ -0.328258               │
│ 0.0                 ┆ -0.000272           ┆ -0.000481           ┆ -0.000618           ┆ … ┆ -0.36185                ┆ -0.350698               ┆ -0.339501               ┆ -0.328258               │
│ 0.0                 ┆ -0.000272           ┆ -0.000481           ┆ -0.000618           ┆ … ┆ -0.36185                ┆ -0.350698               ┆ -0.339501               ┆ -0.328258               │
│ 0.0                 ┆ -0.000272           ┆ -0.000481           ┆ -0.000618           ┆ … ┆ -0.36185                ┆ -0.350698               ┆ -0.339501               ┆ -0.328258               │
└─────────────────────┴─────────────────────┴─────────────────────┴─────────────────────┴───┴─────────────────────────┴─────────────────────────┴─────────────────────────┴─────────────────────────┘

音場の計算 (瞬時値)

use autd3::prelude::*;
use autd3_emulator::*;
use std::time::Duration;

fn main() -> Result<(), Box<dyn std::error::Error>> {
let emulator = Emulator::new([AUTD3::default()]);

let focus = emulator.center() + Vector3::new(0., 0., 150. * mm);

let record = emulator.record(|autd| {
    autd.send(Silencer::disable())?;
    autd.send((
        Static { intensity: 0xFF },
        Focus {
            pos: focus,
            option: Default::default(),
        },
    ))?;
    autd.tick(Duration::from_millis(1))?;
    Ok(())
})?;

let mut sound_field = record.sound_field(
    RangeXY {
        x: focus.x - 20.0..=focus.x + 20.0,
        y: focus.y - 20.0..=focus.y + 20.0,
        z: focus.z,
        resolution: 1.,
    },
    InstantRecordOption {
        sound_speed: 340e3 * mm,
        time_step: Duration::from_micros(1),
        print_progress: true,
        memory_limits_hint_mb: 128,
        gpu: true,
    },
)?;

let df = sound_field.observe_points();
dbg!(df);

let df = sound_field
    .skip(Duration::from_micros(500))?
    .next(Duration::from_micros(500))?;
dbg!(df);
    Ok(())
}

NOTE: gpuオプションは, gpu featureを有効にしている場合のみ使用可能である.

import numpy as np
from pyautd3 import AUTD3, Focus, FocusOption, Silencer, Static, Duration
from pyautd3_emulator import Emulator, RangeXYZ, Recorder, InstantRecordOption

with Emulator([AUTD3()]) as emulator:
    focus = emulator.center() + np.array([0.0, 0.0, 150.0])

    def f(autd: Recorder) -> None:
        autd.send(Silencer.disable())
        autd.send((Static(), Focus(pos=focus, option=FocusOption())))
        autd.tick(Duration.from_millis(1))

    record = emulator.record(f)

    sound_field = record.sound_field(
        RangeXYZ(
            x=(focus[0] - 20.0, focus[0] + 20.0),
            y=(focus[1] - 20.0, focus[1] + 20.0),
            z=(focus[2], focus[2]),
            resolution=1.0,
        ),
        InstantRecordOption(
            sound_speed=340e3,
            time_step=Duration.from_micros(1),
            print_progress=True,
            memory_limits_hint_mb=128,
            gpu=True,
        ),
    )

    df = sound_field.observe_points()
    print(df)

    df = sound_field.skip(Duration.from_micros(500)).next(
        Duration.from_micros(500),
    )
    print(df)

NOTE: 計測点を指定する方法として, Rust版は, RangeXYZ以外に, 列挙順の異なるRangeZXY等や, 2次元専用のRangeXY等, 1次元専用のRangeX等が使用できる. あるいは, 任意の点を指定するために, Vec<Vector3>が使用できる. Python版は, RangeXYZのみが使用できる.

print_progressオプションを有効にすると計算の進捗が表示される. また, gpuオプションを有効にすると, 計算がGPU上で実行される.

膨大なメモリが消費される可能性があるため, next関数によって, 一部時刻のみを取得するようになっている. なお, skip関数を使用することで, 指定した時間だけスキップすることができる.

各観測点における, 出力音圧が時系列順 (単位はtime_stepで指定) に各列に格納される. 各列の名前は, p[Pa]@<時刻>[ns]である. 各行は, observe_pointsで取得できる観測点と対応している.

┌────────────┬───────────┬───────┐
│ x[mm]      ┆ y[mm]     ┆ z[mm] │
│ ---        ┆ ---       ┆ ---   │
│ f32        ┆ f32       ┆ f32   │
╞════════════╪═══════════╪═══════╡
│ 66.625267  ┆ 46.713196 ┆ 150.0 │
│ 67.625267  ┆ 46.713196 ┆ 150.0 │
│ 68.625267  ┆ 46.713196 ┆ 150.0 │
│ 69.625267  ┆ 46.713196 ┆ 150.0 │
│ 70.625267  ┆ 46.713196 ┆ 150.0 │
│ …          ┆ …         ┆ …     │
│ 102.625267 ┆ 86.713196 ┆ 150.0 │
│ 103.625267 ┆ 86.713196 ┆ 150.0 │
│ 104.625267 ┆ 86.713196 ┆ 150.0 │
│ 105.625267 ┆ 86.713196 ┆ 150.0 │
│ 106.625267 ┆ 86.713196 ┆ 150.0 │
└────────────┴───────────┴───────┘
┌──────────────────┬──────────────────┬──────────────────┬──────────────────┬───┬──────────────────┬──────────────────┬──────────────────┬──────────────────┐
│ p[Pa]@500000[ns] ┆ p[Pa]@501000[ns] ┆ p[Pa]@502000[ns] ┆ p[Pa]@503000[ns] ┆ … ┆ p[Pa]@996000[ns] ┆ p[Pa]@997000[ns] ┆ p[Pa]@998000[ns] ┆ p[Pa]@999000[ns] │
│ ---              ┆ ---              ┆ ---              ┆ ---              ┆   ┆ ---              ┆ ---              ┆ ---              ┆ ---              │
│ f32              ┆ f32              ┆ f32              ┆ f32              ┆   ┆ f32              ┆ f32              ┆ f32              ┆ f32              │
╞══════════════════╪══════════════════╪══════════════════╪══════════════════╪═══╪══════════════════╪══════════════════╪══════════════════╪══════════════════╡
│ 22.249609        ┆ 14.994699        ┆ 11.20509         ┆ 3.416157         ┆ … ┆ 167.468948       ┆ 143.434677       ┆ 115.029839       ┆ 78.702179        │
│ 22.973528        ┆ 17.704706        ┆ 14.598668        ┆ 6.462077         ┆ … ┆ 128.688828       ┆ 102.245392       ┆ 73.068733        ┆ 39.29715         │
│ 23.043713        ┆ 20.534163        ┆ 15.762163        ┆ 9.015405         ┆ … ┆ 72.13736         ┆ 50.06559         ┆ 25.516897        ┆ -1.414132        │
│ 21.61729         ┆ 19.986338        ┆ 16.669203        ┆ 11.957072        ┆ … ┆ 6.599095         ┆ -8.152014        ┆ -22.183277       ┆ -34.678905       │
│ 17.479811        ┆ 18.30769         ┆ 16.697689        ┆ 12.929367        ┆ … ┆ -61.525105       ┆ -64.023788       ┆ -62.711498       ┆ -56.925694       │
│ …                ┆ …                ┆ …                ┆ …                ┆ … ┆ …                ┆ …                ┆ …                ┆ …                │
│ 14.888723        ┆ 14.758762        ┆ 13.289121        ┆ 9.737269         ┆ … ┆ -73.181961       ┆ -85.114655       ┆ -92.114983       ┆ -93.623962       │
│ 17.559294        ┆ 15.889968        ┆ 12.346513        ┆ 7.979947         ┆ … ┆ -28.419638       ┆ -61.184322       ┆ -90.326851       ┆ -113.895905      │
│ 18.171673        ┆ 15.26449         ┆ 10.946121        ┆ 5.410897         ┆ … ┆ 19.804182        ┆ -28.594215       ┆ -73.595749       ┆ -115.230705      │
│ 18.815191        ┆ 12.321008        ┆ 7.800498         ┆ 3.680776         ┆ … ┆ 65.869225        ┆ 9.351333         ┆ -48.561874       ┆ -98.87677        │
│ 16.717573        ┆ 10.482997        ┆ 4.044011         ┆ 0.89806          ┆ … ┆ 102.099556       ┆ 44.310383        ┆ -16.616224       ┆ -70.65287        │
└──────────────────┴──────────────────┴──────────────────┴──────────────────┴───┴──────────────────┴──────────────────┴──────────────────┴──────────────────┘

音場の計算 (RMS)

use autd3::prelude::*;
use autd3_emulator::*;
use std::time::Duration;

fn main() -> Result<(), Box<dyn std::error::Error>> {
let emulator = Emulator::new([AUTD3::default()]);

let focus = emulator.center() + Vector3::new(0., 0., 150. * mm);

let record = emulator.record(|autd| {
    autd.send(Silencer::disable())?;
    autd.send((
        Static { intensity: 0xFF },
        Focus {
            pos: focus,
            option: Default::default(),
        },
    ))?;
    autd.tick(Duration::from_micros(25))?;
    Ok(())
})?;

let mut sound_field = record.sound_field(
    RangeXY {
        x: focus.x - 20.0..=focus.x + 20.0,
        y: focus.y - 20.0..=focus.y + 20.0,
        z: focus.z,
        resolution: 1.,
    },
    RmsRecordOption {
        sound_speed: 340e3 * mm,
        print_progress: true,
        gpu: true,
    },
)?;

let df = sound_field.observe_points();
dbg!(df);

let df = sound_field.next(Duration::from_micros(25))?;
dbg!(df);
    Ok(())
}

NOTE: gpuオプションは, gpu featureを有効にしている場合のみ使用可能である.

import numpy as np
from pyautd3 import AUTD3, Duration, Focus, FocusOption, Silencer, Static
from pyautd3_emulator import Emulator, RangeXYZ, Recorder, RmsRecordOption

with Emulator([AUTD3()]) as emulator:
    focus = emulator.center() + np.array([0.0, 0.0, 150.0])

    def f(autd: Recorder) -> None:
        autd.send(Silencer.disable())
        autd.send((Static(), Focus(pos=focus, option=FocusOption())))
        autd.tick(Duration.from_micros(25))

    record = emulator.record(f)

    sound_field = record.sound_field(
        RangeXYZ(
            x=(focus[0] - 20.0, focus[0] + 20.0),
            y=(focus[1] - 20.0, focus[1] + 20.0),
            z=(focus[2], focus[2]),
            resolution=1.0,
        ),
        RmsRecordOption(
            sound_speed=340e3,
            print_progress=True,
            gpu=False,
        ),
    )

    df = sound_field.observe_points()
    print(df)

    df = sound_field.next(Duration.from_micros(25))
    print(df)

NOTE: 最低, は時刻を進める必要がある.

NOTE: RMSで計算される値は, 上記の瞬時音場のRMSではない. 伝搬遅延や振動子の応答を無視した線形重ね合わせである.

各観測点における, 出力音圧のRMSが時系列順 (単位は) で各列に格納される. 各列の名前は, rms[Pa]@<時刻>[ns]である. 各行は, observe_pointsで取得できる観測点と対応している.

┌────────────┬───────────┬───────┐
│ x[mm]      ┆ y[mm]     ┆ z[mm] │
│ ---        ┆ ---       ┆ ---   │
│ f32        ┆ f32       ┆ f32   │
╞════════════╪═══════════╪═══════╡
│ 66.625267  ┆ 46.713196 ┆ 150.0 │
│ 67.625267  ┆ 46.713196 ┆ 150.0 │
│ 68.625267  ┆ 46.713196 ┆ 150.0 │
│ 69.625267  ┆ 46.713196 ┆ 150.0 │
│ 70.625267  ┆ 46.713196 ┆ 150.0 │
│ …          ┆ …         ┆ …     │
│ 102.625267 ┆ 86.713196 ┆ 150.0 │
│ 103.625267 ┆ 86.713196 ┆ 150.0 │
│ 104.625267 ┆ 86.713196 ┆ 150.0 │
│ 105.625267 ┆ 86.713196 ┆ 150.0 │
│ 106.625267 ┆ 86.713196 ┆ 150.0 │
└────────────┴───────────┴───────┘
┌───────────────┐
│ rms[Pa]@0[ns] │
│ ---           │
│ f32           │
╞═══════════════╡
│ 97.675339     │
│ 83.525314     │
│ 60.54364      │
│ 34.229084     │
│ 33.827206     │
│ …             │
│ 51.324219     │
│ 75.84668      │
│ 102.852501    │
│ 120.575188    │
│ 125.698715    │
└───────────────┘

さらなる, Exampleはautd3-emulator (Rust), 及び, pyautd3 (Python)を参照されたい.

高度なトピック

以下では, 上級者向けの設定などを紹介する.

Gainの自作

Rust版のライブラリでは自前のGainを作成することができる.

NOTE: C++, C#, Python版のライブラリでは, この機能は提供されていない. しかし, 同様の目的で, Customを使用することができる.

ここでは, Focusと同じように単一焦点を生成するFocalPointを実際に定義してみることにする.

use autd3::core::derive::*;
use autd3::prelude::*;

#[derive(Gain, Debug)]
pub struct FocalPoint {
    pos: Point3,
}

pub struct Impl {
    pos: Point3,
    wavenumber: f32,
}

impl GainCalculator for Impl {
    fn calc(&self, tr: &Transducer) -> Drive {
        Drive {
            phase: Phase::from(-(self.pos - tr.position()).norm() * self.wavenumber * rad),
            intensity: EmitIntensity::MAX,
        }
    }
}

impl GainCalculatorGenerator for FocalPoint {
    type Calculator = Impl;

    fn generate(&mut self, device: &Device) -> Self::Calculator {
        Impl {
            pos: self.pos,
            wavenumber: device.wavenumber(),
        }
    }
}

impl Gain for FocalPoint {
    type G = FocalPoint;

    fn init(self) -> Result<Self::G, GainError> {
        Ok(self)
    }
}

fn main() {}

Gain::initは各デバイス毎に, GainCalculator (を実装した型) を生成するGainContextGenerator (を実装した型) を返す. 実際の位相, 振幅の計算はGainCalculator::calc関数内で行う.

Modulationの自作

Rust版ではModulationも独自のものを作成することができる. ここでは, 周期中のある一瞬だけ出力するBurstを作ってみる1.

NOTE: C++, C#, Python版のライブラリでは, この機能は提供されていない. しかし, 同様の目的で, Customを使用することができる.

以下が, このBurstのサンプルである.

use autd3::prelude::*;
use autd3::core::derive::*;

#[derive(Modulation, Debug)]
pub struct Burst {
    config: SamplingConfig,
}

impl Burst {
    pub fn new() -> Self {
        Self { 
            config: SamplingConfig::FREQ_4K,
        }
    }
}

impl Modulation for Burst {
    fn calc(self) -> Result<Vec<u8>, ModulationError> {
        Ok((0..4000)
            .map(|i| if i == 3999 { u8::MAX } else { u8::MIN })
            .collect())
    }

    fn sampling_config(&self) -> SamplingConfig {
        self.config
    }
}
fn main() { 
}
1

SDKにはない.

非同期API

Rust版のライブラリでは非同期処理をサポートしている.

NOTE: C++, C#, Python版のライブラリでは, この機能は提供されていない.

Setup

非同期処理を行うには, autd3 crateのasync featureを有効にする必要がある.

cargo add autd3 --features "async"

また, 各Linkに対して, async featureを有効にする必要がある場合がある.

  • SOEM

    cargo add autd3-link-soem --features "async"
    
  • TwinCAT

    cargo add autd3-link-twincat --features "async"
    
  • RemoteTwinCAT

    cargo add autd3-link-twincat --features "remote async"
    
  • RemoteSOEM, Simulator

    • デフォルトで有効

非同期APIを使用するには, 通常のControllerの代わりに, autd3::r#async::Controllerを使用すればいい.

use autd3::prelude::*;
use autd3_link_soem::{SOEM, SOEMOption, Status};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut autd = autd3::r#async::Controller::open(
        [AUTD3 {
            pos: Point3::origin(),
            rot: UnitQuaternion::identity(),
        }],
        SOEM::new(
            |slave, status| {
                eprintln!("slave[{}]: {}", slave, status);
                if status == Status::Lost {
                    std::process::exit(-1);
                }
            },
            SOEMOption::default(),
        ),
    )
    .await?;

    autd.firmware_version().await?.iter().for_each(|firm_info| {
        println!("{}", firm_info);
    });

    autd.send(Silencer::default()).await?;

    let g = Focus {
        pos: autd.center() + Vector3::new(0., 0., 150.0 * mm),
        option: FocusOption::default(),
    };

    let m = Sine {
        freq: 150 * Hz,
        option: SineOption::default(),
    };

    autd.send((m, g)).await?;

    println!("press enter to quit...");
    let mut _s = String::new();
    std::io::stdin().read_line(&mut _s)?;

    autd.close().await?;

    Ok(())
}

ロギングの有効化

ログにはtracingを使用しているため, 以下のようにログ出力を有効化できる.

tracing_subscriber::fmt()
    .with_max_level(tracing::Level::INFO)
    .init();

RUST_LOG環境変数にautd3=<LEVEL>を設定した上で, tracing_initを呼び出すことでログ出力を有効化できる.

<LEVEL>には以下のいずれかを指定することができる. 下に行くほど詳細なログが出力される.

  • ERROR
  • WARN
  • INFO
  • DEBUG
  • TRACE
#include <stdlib.h>

#ifdef WIN32
_putenv_s("RUST_LOG", "autd3=INFO");
#else
setenv("RUST_LOG", "autd3=INFO", false);
#endif

autd3::tracing_init();

RUST_LOG環境変数にautd3=<LEVEL>を設定した上で, Tracing.Initを呼び出すことでログ出力を有効化できる.

<LEVEL>には以下のいずれかを指定することができる. 下に行くほど詳細なログが出力される.

  • ERROR
  • WARN
  • INFO
  • DEBUG
  • TRACE
System.Environment.SetEnvironmentVariable("RUST_LOG", "autd3=INFO");

AUTD3Sharp.Tracing.Init();

RUST_LOG環境変数にautd3=<LEVEL>を設定した上で, Tracing.Initの引数にログファイルへのパスを指定することでログ出力を有効化できる.

<LEVEL>には以下のいずれかを指定することができる. 下に行くほど詳細なログが出力される.

  • ERROR
  • WARN
  • INFO
  • DEBUG
  • TRACE
System.Environment.SetEnvironmentVariable("RUST_LOG", "autd3=INFO");

AUTD3Sharp.Tracing.Init("<path to log file>");

また, AUTD3Sharp.Link.SOEMを使用する場合は, SOEMのログ出力を先に有効化すること.

AUTD3Sharp.Link.SOEM.Tracing.Init("<path to log file>");
AUTD3Sharp.Tracing.Init("<path to log file>");

RUST_LOG環境変数にautd3=<LEVEL>を設定した上で, tracing_initを呼び出すことでログ出力を有効化できる.

<LEVEL>には以下のいずれかを指定することができる. 下に行くほど詳細なログが出力される.

  • ERROR
  • WARN
  • INFO
  • DEBUG
  • TRACE
from pyautd3 import tracing_init

os.environ["RUST_LOG"] = "autd3=INFO"

tracing_init()

Lightweightモード

Rust, 及び, Dart版のライブラリではLightweightモードをサポートしている.

NOTE: Dart版のライブラリはLightweightモードのみサポートしている.

Lightweightモードは, RemoteTwinCAT, RemoteSOEM, Simulator リンクを使用する際に, 通信量を削減するためのモードである.

NOTE: C++, C#, Python版のライブラリでは, この機能は提供されていない. ただし, Lightweightモードの通信にはProtocol Buffersを使用しているため, proto定義に従ってデータを送信すれば, 各言語から使用することは可能.

Setup

lightweightモードのクライアントを使用するには, autd3-protobuf crateのlightweight featureを有効にする必要がある.

cargo add autd3-protobuf --features "lightweight"

サーバ側は, AUTD3 ServerのオプションでLightweightモードを有効にすればいい.

以下が, Lightweightモードを使用するクライアント側のRustのサンプルコードである.

基本的に通常のAPIと同じであるが, 以下の点に注意.

  • GainSTM使用時にautd3_protobuf::lightweight::IntoLightweightGain::into_lightweight()を使用する必要がある
  • group_send使用時にautd3_protobuf::lightweight::Datagram::into_lightweight()を使用する必要がある
  • 一部データはサポートされていない
use autd3::prelude::*;

use autd3_protobuf::lightweight::Controller;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut autd = Controller::open([AUTD3::default()], "127.0.0.1:8080".parse()?).await?;

    println!("======== AUTD3 firmware information ========");
    autd.firmware_version().await?.iter().for_each(|firm_info| {
        println!("{}", firm_info);
    });
    println!("============================================");

    let center = autd.center() + Vector3::new(0., 0., 150.0 * mm);

    let g = Focus {
        pos: center,
        option: Default::default(),
    };
    let m = Sine {
        freq: 150. * Hz,
        option: Default::default(),
    };
    autd.send((m, g)).await?;

{
    // GainSTM requires `autd3_protobuf::lightweight::IntoLightweightGain::into_lightweight()`
    use autd3_protobuf::lightweight::IntoLightweightGain;
    let stm = GainSTM {
        gains: vec![
            Null {}.into_lightweight(),
            Focus {
                pos: center,
                option: Default::default(),
            }
            .into_lightweight(),
        ],
        config: 1.0 * Hz,
        option: Default::default(),
    };
    autd.send(stm).await?;
}

{
    // group_send requires `autd3_protobuf::lightweight::Datagram::into_lightweight()`
    use autd3_protobuf::lightweight::Datagram;
    autd.group_send(
        |dev| Some(dev.idx()),
        std::collections::HashMap::from([
            (0, Null {}.into_lightweight()),
            (
                1,
                (
                    Sine {
                        freq: 150. * Hz,
                        option: Default::default(),
                    },
                    Focus {
                        pos: center,
                        option: Default::default(),
                    },
                )
                    .into_lightweight(),
            ),
        ]),
    )
    .await?;
}

    println!("Press enter to quit...");
    let mut _s = String::new();
    std::io::stdin().read_line(&mut _s)?;

    autd.close().await?;

    Ok(())
}

Dartからの使用例は, autd3-appのリポジトリを参照されたい.

Deviceの自作

Rust版のライブラリではAUTD3以外のDeviceを使用することができる.

NOTE: C++, C#, Python版のライブラリでは, この機能は提供されていない.

NOTE: 実際にはAUTD3以外のデバイスは存在しないので, Emulatorくらいでしか使用することはできない. (Simulatorは未対応.)

ここでは, 振動子の間隔を変えられるCustomDeviceを実際に定義してみることにする.

use autd3::driver::geometry::{Device, Transducer};
use autd3::prelude::*;
use autd3_emulator::Emulator;

struct CustomDevice {
    pitch: f32,
    num_x: usize,
    num_y: usize,
}

impl From<CustomDevice> for Device {
    fn from(dev: CustomDevice) -> Self {
        assert!(0 < dev.num_x * dev.num_y && dev.num_x * dev.num_y <= 256);
        Device::new(
            UnitQuaternion::identity(),
            itertools::iproduct!(0..dev.num_x, 0..dev.num_y)
                .map(|(x, y)| {
                    let x = x as f32 * dev.pitch;
                    let y = y as f32 * dev.pitch;
                    Transducer::new(Point3::new(x, y, 0.))
                })
                .collect(),
        )
    }
}

fn main() {
Emulator::new(
    [CustomDevice {
        pitch: 2.,
        num_x: 16,
        num_y: 16,
    }],
);
}

Deviceは以下の制約を満たす必要がある.

  • 振動子の数は最大で256個
  • 振動子はすべて同一方向を向いている (Device::newの第1引数がすべての振動子の回転を表す)

バージョニング

AUTD3はv8.2.0からセマンティック バージョニングに従っている.

AUTD3 SDKのバージョンはvX.Y.Zと表記される.

  • Xはメジャーバージョンを表し, これが異なるSDK間では互換性は保証されない.
  • Yはマイナーバージョンを表し, 後方互換性を保つような機能追加があった場合に上げられる.
  • Zはパッチバージョンを表し, 後方互換性を保つようなバグ修正があった場合に上げられる.

ファームウェアのバージョン

ファームウェアのバージョンもvX.Y.Zと表記され, セマンティク バージョニングに従う.

ただし, ファームウェアのバージョンはソフトウェアのバージョンとは異なる.

ファームウェアとソフトウェアのバージョン対応表はリリースノートを参照されたい.

FAQ

“No AUTD3 devices found“と表示される

  • macOS, linuxでlink::SOEMを使用する場合, root権限が必要

    • linuxの場合, setcapコマンドで以下の権限を設定することで回避することもできる

      sudo setcap cap_net_raw,cap_net_admin=eip <your executable file>
      
    • macOSの場合, /dev/bpf*ファイルに読み取り権限を追加することで回避することもできる

      sudo chmod +r /dev/bpf*
      
  • (Windows) 最新のnpcapを使用する

  • WSL等の仮想マシンは対応していない

    • VirtualBoxなどで動く場合があるが, 挙動は不安定になる

“One ore more slaves are not responding”, または, “Slow network was detected“と表示される

  • Driverを更新する

    • WindowsでRealtekを利用している場合, 公式サイトからWin10 Auto Installation Program (NDIS)と書かれた方のDriverをインストールすること (Windows 11でも).
  • (Windows) 最新のnpcapを使用する

  • send_cyclesync0_cycleの値を増やす

  • (Windows) デバイスマネージャーの当該ネットワークアダプタのプロパティから, 「電源の管理」タブで「電力の節約のために、コンピューターでこのデバイスの電源をオフにできるようにする」のチェックを外す

link::SOEM使用時に送信が頻繁に失敗する

  • この問題は

    • sync_modeDCにしている

    かつ,

    • オンボードのethernetインターフェースを使用している

    かつ, 以下のいずれかの状況で発生することが確認されている

    • RealSense, Azure Kinect, Webカメラ等を使用する
      • 基本的にカメラをアクティブにした時点で発生
    • 動画や音声を再生する
      • または, インターネットブラウザで動画サイト (Youtube等) を開く
    • Unityを使用する
      • 起動するだけで発生
    • Blenderでアニメーションを再生する
      • その他の操作 (モデリング等) は問題ない
  • この問題の回避策としては, 以下のいずれかを試されたい

    1. sync_modeFreeRunにする
    2. Linuxやmacを使用する.
      • ただし, 仮想マシンはNG
    3. TwinCAT, RemoteTwinCAT, または, RemoteSOEMリンクを使用する
    4. USB to Ethernetアダプターを使用する
      • 少なくとも「ASIX AX88179」のチップを採用しているもので正常に動作することが確認されている
      • なお, オンボードではなくとも, PCIe接続のethernetアダプターでも同様の問題が発生する
  • 上記以外の状況でも発生した, 或いは, 上記状況でも発生しなかった等の報告があれば, GitHubのIssueに積極的に報告していただけると幸いである.

リンクが頻繁に途切れる

  • 超音波の出力時にこれが頻発する場合は, 電力が足りているかを確認すること
    • デバイス一台で最大50W消費する

RemoteTwinCATリンク使用時にエラーが出る

  • ファイアウォールでブロックされている可能性があるため, ファイアウォールを切るか, TCP/UDPの48898番ポートを開ける.
  • クライアントPCのサーバー以外とのLANをすべて切断する.

振動子の位相/振幅データにアクセスするには?

  1. 自分で所望のGainを作成する. Gainの自作を参照.

AM変調データにアクセスするには?

  1. 自分で所望のModulationを作成する. Modulationの自作を参照.

その他

引用

本SDKを使用する場合は, 以下の論文の引用を検討されたい.

LICENSE

autd3ライブラリそれ自体はMITライセンスである.

autd3ライブラリはいくつかのサードパーティライブラリに依存している. これらのライセンスの詳細は各リポジトリを参照すること.

Firmware v10 vs v11

本ソフトウェアは以下のファームウェアをサポートしている. ただし, ファームウェアv10.0.1を使用する場合, 一部機能は未サポートである.

詳細は以下の表を参照されたい.

v10.0.1v11.0.0
Modulationの最大バッファサイズ32768165536
FociSTMの最大パターン数焦点数によらず8192パターン1
e.g., , , …,
総焦点数が65536点
e.g., , , …,
PulseWidthEncoderサポートしていないサポートしている
GPIOOutputType::SysTimeサポートしていない2サポートしている
FPGAの推定消費電力
1

範囲外のデータを使用した場合は, エラーにはならず単に上書きされるような動作になることに注意.

2

エラーにはならないが, 正常に動作しない.

Release Notes

DateSoftware VersionFirmware Version
2025/03/2632.0.111.0.0/10.0.11
2025/03/2432.0.011.0.0/10.0.11
2025/03/0731.0.110.0.1
2025/02/2430.0.110.0.1
2025/02/1029.0.010.0.1
2024/10/1528.1.010.0.0
2024/10/1428.0.110.0.0
2024/10/1228.0.010.0.0
2024/08/0927.0.09.0.0
2024/06/2926.0.08.0.1
2024/06/1825.3.28.0.1
2024/06/1725.3.18.0.1
2024/06/1425.2.38.0.1
2024/06/1025.1.08.0.1
2024/06/0825.0.18.0.0
2024/06/0625.0.08.0.0
2024/05/2224.1.07.0.0
2024/05/1824.0.07.0.0
2024/05/1323.1.07.0.0
2024/05/1123.0.17.0.0
2024/05/1123.0.07.0.0
2024/04/0822.1.06.1.x
2024/03/3022.0.46.0.x
2024/02/2522.0.16.0.x
2024/01/2921.1.05.1.x
2024/01/2621.0.15.1.x
2024/01/1120.0.35.0.x
2024/01/0520.0.05.0.x
2023/12/1419.1.04.1.x
2023/12/1019.0.04.1.x
2023/12/0418.0.14.0.x
2023/12/0218.0.04.0.x
2023/11/2917.0.34.0.x
2023/11/2817.0.24.0.x
2023/11/2717.0.14.0.x
2023/11/2717.0.04.0.x
2023/10/1416.0.03.0.x
2023/10/0415.3.13.0.x
2023/10/0415.3.03.0.x
2023/09/2915.2.13.0.x
2023/09/2815.2.03.0.x
2023/09/2215.1.23.0.x
2023/09/1515.1.13.0.x
2023/09/1415.1.03.0.x
2023/09/1415.0.23.0.x
2023/08/0714.2.2N/A
2023/08/0114.2.1N/A
2023/07/2814.2.0N/A
2023/07/2714.1.0N/A
2023/07/1914.0.1N/A
2023/07/1814.0.0N/A
2023/07/1113.0.0N/A
2023/07/0412.3.1N/A
2023/07/0412.3.0N/A
2023/06/2412.2.0N/A
2023/06/2312.1.1N/A
2023/06/2212.1.0N/A
2023/06/2112.0.0N/A
2023/06/1211.1.0N/A
2023/06/0911.0.2N/A
2023/06/0911.0.1N/A
2023/06/0811.0.0N/A
2023/05/3010.0.0N/A
2023/05/019.0.1N/A
2023/04/299.0.0N/A
2023/04/268.5.0N/A
2023/04/238.4.1N/A
2023/04/188.4.0N/A
2023/03/218.3.0N/A
2023/03/108.2.0N/A
2023/02/198.1.2N/A
2023/02/038.1.1N/A
2023/02/028.1.0N/A
2023/01/242.8.0N/A
2023/01/182.7.6N/A
2023/01/172.7.5N/A
2023/01/122.7.4N/A
2022/12/292.7.2N/A
2022/12/242.7.1N/A
2022/12/162.7.0N/A
2022/12/132.6.8N/A
2022/12/102.6.7N/A
2022/12/062.6.6N/A
2022/11/282.6.5N/A
2022/11/222.6.4N/A
2022/11/212.6.3N/A
2022/11/152.6.2N/A
2022/11/132.6.1N/A
2022/11/102.6.0N/A
2022/11/082.5.2N/A
2022/11/062.5.1N/A
2022/11/042.5.0N/A
2022/10/252.4.5N/A
2022/10/212.4.4N/A
2022/10/182.4.3N/A
2022/10/142.4.2N/A
2022/10/092.4.1N/A
2022/09/282.4.0N/A
2022/08/142.3.1N/A
2022/08/082.3.0N/A
2022/06/292.2.2N/A
2022/06/222.2.1N/A
2022/06/102.2.0N/A
2022/06/022.1.0N/A
2022/05/252.0.3N/A
2022/05/242.0.2N/A
2022/05/222.0.1N/A
2022/05/172.0.0N/A
1

一部機能は未サポート. 詳細はFirmware v10 vs v11を参照.

デベロッパーズマニュアル

以下では, 本ライブラリ, 及び, ファームウェアの実装について述べる.

なお, ソースコードが主であり, この文章自体はソースコードの補足としての位置づけであることに注意されたい.

FPGAファームウェア

以下では, FPGAのファームウェアについて説明する. なお, ファームウェアバージョンは11.0.0である.

開発環境構築

FPGAの開発にはVivado 2024.1を使用している. ML Editionは無料 (要登録) なので各自インストールされたい. インストール時にDesign Toolsの“Vivado“とDeviceの“Artix-7“, 及び, Cable Drivesをインストールすること. Vitisは必要ない.

Vivadoのプロジェクトを生成するには, 以下のようにする.

git clone https://github.com/shinolab/autd3-firmware.git
cd autd3-firmware/src/fpga
cargo make build

これで, autd3-fpga.xprファイルが生成されるはずなので, これをVivadoで開けば良い.

VivadoのGUI操作でビットストリームファイルを作成できる. ビットストリームファイル (.bit) からコンフィグレーションファイル (.mcs) を生成し, FPGAに書き込むには, 「Tools」→「Run Tcl Script…」でconfiguration.tclを実行すれば良い.

vivadoコマンドやtclコマンドの使い方はXilinxのUser’s Guide (UG835, UG910) を参照されたい.

概要

FPGAにおける信号生成の流れを以下に示す. なお, あくまで概念図なので実際の実装とは都合により異なる.

FPGA内部の概要図

まず, CPUから送られてきたデータはすべてMemoryモジュール内のBlock RAM (BRAM) に格納される. STMモジュールは一定のサンプリングレート () で振幅/位相データをBRAMからサンプリングする. その後, 振幅データはModulationモジュール内で変調データと掛け合わされる. 続けて, 振幅データはSilencerモジュールにより静音化処理 (急峻な変化を抑える処理) が施され, Pulse Width Encoderモジュールでパルス幅に変換され, 最後にPWMモジュールに渡される. 位相データはSilencerモジュールに渡された後, PWMモジュールに渡される. 最後にパルス幅/位相データからPulse Width Modulation (PWM) 波形の出力が計算される.

以下に, 各モジュールの解説をしていく. なお, 説明の都合上, 信号の流れる順とは異なる順序で説明する.

PWM

PWMモジュール
NameIn/OutWidthDescription
CLKIn120.48MHzクロック
TIME_CNTIn8超音波周期カウンタ
UPDATEIn1超音波周期トリガ
DIN_VALIDIn1パルス幅/位相データ有効フラグ
PULSE_WIDTHIn9パルス幅
PHASEIn8位相
PWM_OUTOut1x249PWM信号
DOUT_VALIDOut1立ち上がり/立ち下がりデータ有効フラグ (デバッグ用)

Preconditioner

このサブモジュールでは, 後の処理を楽にするために, パルス幅/位相を信号の立ち上がり時刻と立ち下がり時刻に変換する.

ここで, は周期である.

NOTE: を計算しているのは, が奇数の場合を考慮するためである.

なお, Preconditionerの計算はパイプライン的に行われる. 入力として, DIN_VALIDは249クロックの間1であり, その間PULSE_WIDTH, PHASEからパルス幅/位相データが順番に流れてくることを想定している. 出力に関して, 中間データを出力しないようにするために, 一旦バッファに格納している.

Buffer

Bufferは, TIME_CNTの値がになるまでの更新を遅らせる. すなわち, 超音波周期の途中でが更新されることを防ぐためにある.

Generator

GeneratorはTIME_CNTの値とBufferから出力されたを用いて, PWMの出力を計算する.

Pulse Witdth Encoder

NameIn/OutWidthDescription
CLKIn1メインクロック
DUTY_TABLE_BUSIn-メモリバス
DIN_VALIDIn1強度/位相データ有効フラグ
INTENSITY_INIn8強度
PHASE_INIn8位相
PULSE_WIDTH_OUTOut9パルス幅
PHASE_OUTOut8位相
DOUT_VALIDOut1パルス幅/位相データ有効フラグ

このモジュールは, 強度をパルス幅に変換する. 強度とパルス幅の関係は, 理想的には となる. ここで, は周期である. このモジュールはこれの逆変換を行うためのモジュールである.

NOTE: 最初からパルス幅を使用しないのは, 強度にAM変調をかけるためである.

ただし, 上記は理想的な変換であり, 現実とは異なる可能性がある. そのため, このモジュールでは, 強度をインデックス, パルス幅を値とするテーブルを利用している. このテーブルは, 書き込み可能になっているため, 任意の変換を実現する事ができる. (デフォルトでは, 上記の理想的な変換を行うテーブルが用意されている.)

このモジュールの処理もパイプライン的に行われる. 入力として, DIN_VALIDは249クロックの間1であり, その間INTENSITY_IN, PHASE_INから強度/位相データが順番に流れてくることを想定している. 処理が終わったら, DOUT_VALIDを1とし, 順次PULSE_WIDTH_OUT, PHASE_OUTからパルス幅/位相データを流す.

NOTE: 位相に関しては何も処理しないが, ディレイを入れてパルス幅データと歩調を揃えている.

Silencer

Silencerモジュール
NameIn/OutWidthDescription
CLKIn1メインクロック
SETTINGSIn-
├─ FLAGIn8フラグ
├─ UPDATE_RATE_INTENSITYIn16固定更新幅 (強度)
├─ UPDATE_RATE_PHASEIn16固定更新幅 (位相)
├─ COMPLETION_STEPS_INTENSITYIn16固定完了ステップ (強度)
└─ COMPLETION_STEPS_PHASEIn16固定完了ステップ (位相)
DIN_VALIDIn1強度/位相データ有効フラグ
INTENSITY_INIn8強度
PHASE_INIn8位相
INTENSITY_OUTOut8強度
PHASE_OUTOut8位相
DOUT_VALIDOut1強度/位相データ有効フラグ

このモジュールは, 強度/位相データの急激な変化を抑え, 静音化するためのモジュールである.

強度データに対しては, 現在の値と, 目標値に対して, 以下のように更新する. 出力はとなる. また, 位相データに対しては, 現在の値と, 目標値に対して, 以下のように更新する. 出力はとなる. ここで, はそれぞれ強度, 位相に対する1ステップ当たりの更新量, は超音波周期 (=) である. なお, 更新はの周波数で行われる.

NOTE: 強度, 位相データを256倍しているのは, より細かい更新を可能にするためである.

NOTE: 位相データの場合分けは, 位相の周期性によるものである. 位相変化が半周期より大きい場合は, 逆方向に変化したほうが早く変化が完了する.

の決定方法に2つのモードがある.

  1. 固定更新幅モード: すべての振動子に対して, 強度, 位相それぞれ固定のUPDATE_RATE_INTENSITY, UPDATE_RATE_PHASEを用いる.
  2. 固定完了ステップモード: すべての振動子に対して同一のステップで変化が完了するようにを決定する. すなわち, 強度, 位相のステップ数COMPLETION_STEPS_INTENSITY, COMPLETION_STEPS_PHASEに対して, を, 目標値が変化したタイミングでここの振動子に対して設定する.

これらのモードはFLAGにて切り替える.

このモジュールの処理もパイプライン的に行われる. 入力として, DIN_VALIDは249クロックの間1であり, その間INTENSITY_IN, PHASE_INから強度/位相データが順番に流れてくることを想定している. 処理が終わったら, DOUT_VALIDを1とし, 順次INTENSITY_OUT, PHASE_OUTから強度/位相データを流す.

Step Calculator

このサブモジュールは, 式(1)によりを計算する.

ただし, 整数を用いるため, 余りの処理が問題になる. これに関しては, 例えば強度の場合, ステップの内, 最初のステップにおいては, それ以降ではとして使用することで対処している. 位相も同様である.

なお, 固定更新幅モードの場合は, このサブモジュールの出力は使用されない.

Interpolator

このサブモジュールは, 現在の強度/位相データに対して, 更新量を加算しているだけである. ただし, 位相に関しては前述の通り, 目標値 (の256倍) と現在値との差の大きさに応じて加算するか減算するかを決定している.

Modulation

Modulationモジュール
NameIn/OutWidthDescription
CLKIn1メインクロック
SETTINGSIn-
├─ REQ_RD_SEGMENTIn1要求読み込みセグメント
├─ CYCLE[]In16セグメントの周期
├─ FREQ_DIV[]In16セグメントの周波数分周比
├─ REP[]In16セグメントの繰り返し回数
├─ TRANSITION_MODEIn8遷移モード
└─ TRANSITION_VALUEIn64遷移値
SYS_TIMEIn57システム時刻
DIN_VALIDIn1強度/位相データ有効フラグ
INTENSITY_INIn8強度
PHASE_INIn8位相
INTENSITY_OUTOut8強度
PHASE_OUTOut8位相
DOUT_VALIDOut1強度/位相データ有効フラグ
MOD_BUSIn-変調用メモリバス

このモジュールは, 強度データに変調データを掛け合わせることでAM変調を実現するモジュールである. 変調データは一定の周期でメモリからサンプルされる. 変調データ領域は2つのセグメントに分けられており, それぞれ独立に設定できる.

また, 位相に関しては強度データと歩調を合わせるためにここでディレイを入れている.

Timer

このサブモジュールは, システム時刻, 周期, 周波数分周比から, 現在読み込むべき変調データのインデックスを計算する. 変調データのインデックス として計算される. SYS_TIMEでカウントアップされため, AMデータのサンプリング周波数 となり, はこの周波数でからまで周期的にカウントアップされる.

SYS_TIMEがすべてのデバイスで同期しているため, このインデックスも必然的にすべてのデバイスで同期する.

NOTE: SETTINGSで指定するCYCLEであることに注意する.

Swapchain

このサブモジュールはセグメントの切り替えを制御する.

セグメント切替時の挙動は以下の通りである.

  1. 要求されたセグメントが1の場合
    1. 第1セグメントの繰り返し回数が0xFFFFの場合, 直ちにセグメントを1に切り替え, 無限ループする.
      • 遷移モードがEXTの場合, セグメントないのデータを一周するたび, セグメントを自動的に切り替える.
    2. 第1セグメントの繰り返し回数が0xFFFF以外の場合, 遷移モードに従って, セグメントの切り替えを待機する. セグメント切り替え後, 指定回数の繰り返しが終わった後, STOPをアサートする.
      • 遷移モードがSYNC_IDXの場合, 第1セグメントのインデックスが0になったら, セグメントを1に切り替える.
      • 遷移モードがSYS_TIMEの場合, システム時間が遷移値で指定された値になったらセグメントを1に切り替え, インデックスを0に初期化.
      • 遷移モードがGPIOの場合, 遷移値で指定されたGPIO Inピンがアサートされたらセグメントを1に切り替え, インデックスを0に初期化
  2. 要求されたセグメントが0の場合も同様

NOTE: SETTINGSで指定するREPは繰り返し回数であることに注意する.

Multiplier

このサブモジュールは, 強度データに変調データを掛け合わせ, で割る.

変調データはSwapchainモジュールから渡される, SEGMENT, IDXに応じてメモリから読み出す. なお, STOPフラグがアサートされている間は, セグメント及びインデックスを更新しない.

STM

STMモジュール
NameIn/OutWidthDescription
CLKIn1メインクロック
SETTINGSIn-
├─ REQ_RD_SEGMENTIn1要求読み込みセグメント
├─ CYCLE[]In16セグメントの周期
├─ FREQ_DIV[]In16セグメントの周波数分周比
├─ REP[]In16セグメントの繰り返し回数
├─ SOUND_SPEED[]In16セグメントの音速
├─ TRANSITION_MODEIn8遷移モード
└─ TRANSITION_VALUEIn64遷移値
SYS_TIMEIn57システム時刻
INTENSITYOut8強度
PHASEOut8位相
DOUT_VALIDOut1強度/位相データ有効フラグ
STM_BUSIn-位相フィルタ用メモリバス

本モジュールは, 強度/位相データを出力する.

Timer

このサブモジュールは, システム時刻, 周期, 周波数分周比から, 現在読み込むべきデータのインデックスを計算する. 変調データのインデックス として計算される. SYS_TIMEでカウントアップされため, データのサンプリング周波数 となり, はこの周波数でからまで周期的にカウントアップされる.

SYS_TIMEがすべてのデバイスで同期しているため, このインデックスも必然的にすべてのデバイスで同期する.

NOTE: SETTINGSで指定するCYCLEであることに注意する.

Swapchain

このサブモジュールはセグメントの切り替えを制御する.

セグメント切替時の挙動は以下の通りである.

  1. 要求されたセグメントが1の場合
    1. 第1セグメントの繰り返し回数が0xFFFFの場合, 直ちにセグメントを1に切り替え, 無限ループする.
      • 遷移モードがEXTの場合, セグメントないのデータを一周するたび, セグメントを自動的に切り替える.
    2. 第1セグメントの繰り返し回数が0xFFFF以外の場合, 遷移モードに従って, セグメントの切り替えを待機する. セグメント切り替え後, 指定回数の繰り返しが終わった後, STOPをアサートする.
      • 遷移モードがSYNC_IDXの場合, 第1セグメントのインデックスが0になったら, セグメントを1に切り替える.
      • 遷移モードがSYS_TIMEの場合, システム時間が遷移値で指定された値になったらセグメントを1に切り替え, インデックスを0に初期化.
      • 遷移モードがGPIOの場合, 遷移値で指定されたGPIO Inピンがアサートされたらセグメントを1に切り替え, インデックスを0に初期化
  2. 要求されたセグメントが0の場合も同様

NOTE: SETTINGSで指定するREPは繰り返し回数であることに注意する.

Gain

このモジュールは, 指定されたインデックスのデータを順次出力するだけである.

Focus

このモジュールはBRAMに書き込まれた焦点の位置データ列から適切な位相を計算して出力する.

焦点の位置データはデバイスにローカルな座標系で表現される. また, 位置データは符号あり固定小数点数で表現される. この単位はである.

このモジュールでは, 焦点位置データと, 振動子の位置データ, 及び, 音速 (SOUND_SPEED, ) から, 振動子の位相を計算する. ここで, 音速の単位は, である. また, 振動子のローカル座標における位置データはあらかじめ計算しておくことができるため, これを専用のBRAMに格納しておく.

NOTE: 振動子の位置データはtr.coeに格納されている.

まず, の間の距離を計算する. また, 音速から波長を計算すると, となる. よって, 位相 と計算できる.

NOTE: に対応することに注意.

NOTE: 音速の単位が複雑なのは以下の理由による. まず, 振動子-焦点間距離の計算において, 各座標の入力値が符号あり固定小数点数なので, その値の二乗値のビット幅はとなる. したがって, 距離の値のビット幅になる. 音速の単位を上記のようにしておくと, 位相の計算の際, 分母の計算が楽になり, その値をの範囲に収められる.

多焦点

Focusモジュールは上記の計算を同時8焦点分行い, 各焦点の位相から, 以下の計算により, 最終的な位相データを求める.

ここで, は焦点の数である.

の計算に関してはの有限値であるため, あらかじめ計算済みのテーブルを用いる. また, の計算も, 計算済みのテーブルを用いる. のテーブルはデータ量を削減するため, の平均値, の平均値に対して, をキーとしている.

Synchronizer

NameIn/OutWidthDescription
CLKIn1メインクロック
SETTINGSIn-
├─ UPDATEIn1同期フラグ
└─ ECAT_SYNC_TIMEIn64同期EtherCAT時刻
ECAT_SYNCIn1EtherCAT Sync0信号
SYS_TIMEOut57システム時刻
SKIP_ONE_ASSERTOut11飛ばしアサート

Synchronizerはすべてのデバイスで同期した時刻SYS_TIMEを生成するモジュールである. 超音波出力, Modulation, STM等はすべてこのSYS_TIMEを時刻の基準にしている. したがって, それらも必然的にすべてのデバイスで同期する.

このモジュールの動作を理解するためには, 以下の事柄を理解しておく必要がある.

  • EtherCATのシステム時刻は単位のデータであり, これはすべてのEtherCATスレーブで同期している.
    • なお, 時刻基準は2000年1月1日である. 単位のなので約600年しか使えないが, 事実上問題ない.
  • FPGAからはEtherCATスレーブ (CPUボード) のレジスタに書き込まれているシステム時刻に直接アクセスできない.
  • EtherCATスレーブは一定の周期でSync0信号 (ECAT_SYNC) をアサートできる. この信号の発火はシステム時刻を参照しているので, すべてのデバイスで同期している.
  • Sync0信号が発火する周期はの整数倍である.

なお, SYS_TIMEはEtherCATのシステム時刻に同期しているが, その単位はで, となっている.

NOTE: SYS_TIMEにしたのは可能な限り使用リソースを少なくするためである. これにより, オーバーフローまでの期限がEtherCATそのものより短く, 約223年となるが, これも事実上問題ないだろう.

まず, SYS_TIMEの初回の時刻同期はCPUファームウェアと協調して, 以下の手順で行われる.

  1. CPU: 次のSync0信号が発火するシステム時刻ECAT_SYNC_TIMEを読み出し, FPGA内のBRAMに書き込む.
    • ただしこの時, 現在時刻がECAT_SYNC_TIMEに近すぎる場合は, いったん待機する. これにより, 次のUPDATEフラグの書き込み/読み出しがECAT_SYNC_TIMEより前に行われることを保証する.
  2. CPU: UPDATEフラグをFPGA内のBRAMに書き込む.
  3. FPGA: 以下の計算により, 時刻単位を変換しておく.
  4. FPGA: UPDATEフラグがアサートされている, かつ, ECAT_SYNCがアサートされたタイミングで, 単位変換済みのECAT_SYNC_TIMESYS_TIMEにセットする.

以上により, FPGA内部のSYS_TIMEの時刻がEtherCATのシステム時刻に同期する.

NOTE: ただ単にデバイス間を同期することが目的なのであれば, FPGA内部の時刻をEtherCATのシステム時刻に同期する必要はない. そのため, UPDATEフラグがアサートされている, かつ, ECAT_SYNCがアサートされたタイミングでSYS_TIMEに初期化すれば良い気もするが, これは正しくない. というのも, EtherCATスレーブのシステム時刻とSync0信号は同期しているが, それ以外の動作が同期している保証はないためである. つまり, UPDATEフラグがアサートされるタイミングはすべてのデバイスで異なる可能性がある.

あとは, SYS_TIMEをメインクロックでカウントアップすればいい. しかし, 実際にはFPGAのクロックを生成する水晶振動子には個体差があるため, SYS_TIMEは徐々にずれていってしまう. 定期的に上記と同様のことを行って補正してもいいが, それは大変なので, Sync0信号が周期的かつ同期的に発火するという性質を用いた別の補正方法を採用した.

まずは, Sync0信号がの間隔で発火する場合を考えよう. この場合, Sync0信号が発火した際, SYS_TIMEは前回Sync0信号が発火したときの値にを足した値になっているはずである. したがって, Sync0信号が発火した際に, この本来あるべき値と実際の値を比較して補正できる.

Sync0信号がの間隔で発火する場合は, を推定する必要がある1. これは, Sync0信号の発火タイミングから推定できる. Sync0信号の発火タイミングで, カウンタで初期化した後, これをメインクロックでカウントアップする. そして, 次にSync0信号の発火タイミングで で推定する. この推定が正しくなる条件を求めてみる. FPGAの実際のクロック周波数がであるとすると, となる. これが正しくになるためには, から, を満たせば良い (十分条件. なお, の場合は常に満たされる.). AUTD3デバイスで使用している水晶振動子 (SG-8002CE-25.6M-PCB-L2, EPSON) の周波数は, トレランスはである. これから生成されるクロックを, FPGA内蔵のMixed-Mode Clock Manager (MMCM) で変換したものをメインクロックとして使用している. トレランスは不変と仮定すると2, の最大値は となる. このときの条件は, である. 実際には, 10台ほどのデバイスを接続したとしてもで動作するので, この条件は常に満たされると考えて問題ない.

さて, SYS_TIMEの補正は, SYS_TIMEが基準より進んでいる場合 (即ち, 水晶振動子の周波数が基準より高い場合) はSYS_TIMEのインクリメントを止め, SYS_TIMEが遅れている場合 (即ち, 水晶振動子の周波数が基準より低い場合) はSYS_TIMEすることにより実行する. そのため, SYS_TIMEは逆戻りすることはないが, 一つ飛ばしでカウントアップされる可能性がある. 一つ飛ばしが発生した際はSKIP_ONE_ASSERTをアサートする.

NOTE: SYS_TIMEをの補正をSync0信号が発火した後すぐ集中的に行うと, の周期的な操作に由来するノイズが生じる. 特によく使われるあたりの周波数はヒトの聴覚が比較的敏感な周波数でもあるので, このノイズは避けたいところではある. 古いバージョンでは補正タイミングを疑似乱数を用いてバラすことでこれを回避していた. しかし実際にはこのノイズはほぼ聞こえないので, 現在はこのような処理は行っていない.

1

ソフトウェアから指定すればいいだけの話だが, TwinCATを使用する場合, このを (クライアント側から) 取得する方法がなかった.

2

これが正しいかは確かめてはいない. また, MMCMの精度やジッター等は無視しているが, 後に明らかになるように, かなり余裕があるので問題ないだろう.

Time Count Generator

NameIn/OutWidthDescription
CLKIn1メインクロック
SYS_TIMEIn57システム時刻
SKIP_ONE_ASSERTIn1Synchonizer参照
TIME_CNTOut9カウンタ
UPDATEOut1超音波周期トリガ

このモジュールは, 同期システム時刻 SYS_TIMEから, 離散時刻 (TIME_CNT) と超音波周波数で発火するUPDATEを生成する. SYS_TIMEはメインクロックでカウントアップされる符号なし整数カウンタである. SYS_TIMEの値は, 全デバイス間で同期しているため, も必然的に全てのデバイスの全振動子で同期する. SYS_TIMEに関しては, Synchonizerを参照されたい.

TIME_CNTの生成には, SYS_TIMEの下位9bitをそのまま使用する. また, UPDATEは基本的にTIME_CNTが特定の値のときだけとすれば良い. ただし, SYS_TIMEは補正のために, 値が1飛ばしでインクリメント (すなわち前回の値) される, または, インクリメントされない可能性があるため, 単純にUPDATETIME_CNTのときにのようにすることができない. そのため, UPDATEがアサートされるのは, TIME_CNTからまたはに変化するタイミング, としている. (すなわち, UPDATEがアサートされたクロックの次のクロックからTIME_CNTがリセットされ, 新しい周期が始まるようになっている.)

Controller

Controllerモジュールは基本的に制御用のデータを読み出すだけのモジュールである.

中身は巨大なステートマシンになっている. これを手で書くのは大変なので, gen_cnt_state_machine.pyスクリプトで自動生成するようにしている.

Memory

このモジュールはBRAMを集めたモジュールである.

FPGA内部のBRAMは5つに分かれている. 書き込み時はCPU_ADDR () の内, 上位 (BRAM_SELECT) でこれを区別する.

  • 0x0: Controller BRAM
  • 0x1: Modulation BRAM
  • 0x2: Pulse Witdth Encoder Table BRAM
  • 0x3: STM BRAM

なお, Modulation/STM BRAMはそのままだと書き込みアドレスが足りないので, Controller Main BRAM内の特定のアドレスに書き込まれたページ番号をアドレスの上位に付加することでアドレスを拡張している. また, Modulation/STM BRAMは, 書き込むセグメントの選択もController Main BRAM内の特定のアドレスに書き込まれたセグメント番号により行う.

これらのページ番号, セグメント番号データはCPUバスのクロックドメインで使用するため, BRAMに格納されたデータは使用しない. 代わりに, このアドレスへの書き込みを監視し, 直接レジスタに格納して使用するようにしている.

CPUボードとのインターフェース

FPGAとCPUボードとの接続は以下のようになっている.

  • [16:0] CPU_ADDR: Input, CPUボードから書き込まれるデータのアドレス. 最下位のは使用できないため, 実質
  • [15:0] CPU_DATA: Input/Output, CPUボードから書き込まれるデータ
  • CPU_CKIO: Input, CPUバスクロック ()
  • CPU_CS1_N: Input, CPUバスイネーブル (負論理)
  • CPU_WE0_N: Input, 書き込みイネーブル (負論理)
  • CPU_WE1_N: Input, 書き込みイネーブル (負論理, 未使用)
  • CPU_RD_N: Input, 読み込みイネーブル (負論理)
  • CPU_RDWR: Input, 1でCPUからの読み込み, 0でCPUからの書き込み

CPUとの通信には, CPU_ADDR, CPU_DATA, CPU_CKIO, CPU_CS1_N, CPU_WE0_N, 及び, CPU_RDWRを使用する. これらの信号は, XilinxのBRAM IP (Native Port) と接続できる.

CPUからの書き込みには, CPU_ADDRaddr, CPU_DATAdin, CPU_CKIOclk, ~CPU_CS1_Nen, ~CPU_WE0_Nweに接続すれば良い.

CPUからの読み込みには, トライステートバッファを使用し, ~CPU_CS1_N, かつ, ~CPU_RD_N, かつ, CPU_RDWRの場合にCPU_DATAdoutに接続し, そうでないときはCPU_DATAをハイインピーダンス (z) にする.

CPUファームウェア

TODO…

開発環境構築

TODO…

ソフトウェア

TODO…

理論と考察

以下では, フェーズドアレイを使用する際に必要となる理論について述べる.

PWMと超音波の出力

Author: Shun Suzuki

Date: 2024-01-05


まず, 周期がであり, 時間に対する電圧 で与えられるPWM信号を考える.

この信号のフーリエ展開は から となる.

周波数の振動子が十分高い値を持つとすると1, 周波数の成分だけが取り出され, 超音波として放出される. PWM信号の周波数の成分は, であるため, PWM信号のパタメータと放出される超音波の振幅と位相の関係式は と表される. すなわち, パラメータにより, それぞれ位相と振幅を制御できる.

1

実際, AUTD3で使用されるT4010A1/T4010B1の値はであり, の成分との成分との比はとなり十分無視できる.

フェーズドアレイについて

Author: Shun Suzuki

Date: 2024-01-06


ここでは, 多数の球面波音源が生成する音場について考察する.

フェーズドアレイから放射される音場

位置にある振動子が生成する音圧場は次の式で与えられる (球面波モデル). ここで, はそれぞれ振動子の振幅, 位相パラメータであり, は波数, は角周波数, は時間である1.

ここで, 次の2つの関数を導入する. このを伝達関数, を振動子のゲインと呼ぶことにする. は振動子の物理的な配置で決まるのに対して, は振動子に印加する駆動信号によってプログラマブルに変更できる.

ここで, 複数の振動子からなるアレイを考える. 各振動子を添字で区別することにすると, これらの振動子が生成する音場は, 全ての振動子の音場の重ね合わせになり, と表される2.

格子状のアレイ

以下では, フェーズドアレイは平面に格子状に並べられた振動子から構成されると仮定する.

NOTE: 実際には, 格子状である必然性はない. 例えば, 3では曲面状に振動子を配置しているし, 4ではさまざまな形状のアレイを生成できるようにしている. さらに変わったところだと, 5ではアレイの配置をFibonacci spiralに沿って配置している. この論文では, Fibonacci spiral配置によりグレーティングローブ (後述) が軽減されると報告している. しかし, ここでは簡単のため平面上の格子配列を考える.

このとき, 振動子は間隔で縦横それぞれ個並べられているとしよう. このとき, である. ここで, 列の振動子を表す.

焦点の生成

ある, 位置に焦点を生成したいとする. つまり, を最大化しようとしたときに振動子に与えるべきゲインは明らかに となる. ここで, は振動子の上限振幅である.

このときに, 焦点付近の音場がどうなるか考察しよう. 解析を簡単にするために, いくつかの近似を導入する. まず, 列の振動子がに置かれているとすると, となる. そのため, となる. ここで十分遠方を考え, が成り立つとしよう. このとき, となる. この近似をFresnel近似と呼ぶ. 同様に, が成り立つならば となる. この近似をFraunhofer近似と呼ぶ. さらに, ここで次の近似を導入する. この近似を近軸近似と呼ぶ.

これらの近似を用いると, となる.

ここで, 焦点を通り, アレイに平行な平面, すなわちの場合を考えよう. このとき, となる.

さてここで, であり, であるから と表される. ここで, である.

したがって, となる.

ここで, sinc関数の挙動を確認しておこう. 以下にのグラフを載せる.

図からわかるようにになる.

また, 以下にのグラフを載せる.

図からわかるように, でその絶対値が最大値のをとることがわかる.

したがって, はまず, で最大値をとる. その後, 軸方向には, になる. 同様に, 軸方向には になる. 従って, x,y軸方向の焦点の幅はそれぞれ, となる. フェーズドアレイの幅が, x,y軸方向にそれぞれであることを考えると, 波長程度に焦点を収束できるのは (フェーズドアレイから焦点までの距離) がフェーズドアレイの幅程度までとなる.

また, でも最大値をとる. これがグレーティングローブと呼ばれるものである.

以下にの場合の焦点平面のx方向の音場を載せる. 図に見られるように, 焦点周りの音場は比較的精度よく近似されている. しかし, グレーティングローブ周りの近似はかなりずれている事がわかる. これは, , 及び近軸近似が成り立たないためである.

x軸方向の音場 (). 焦点付近は比較的よく近似されるが, グレーティングローブ周りは近似の精度は低く, 位置も音圧もずれている.
1

より現実に近いモデルとして, さらに振動子の指向性や空気の吸収による減衰などを入れたものがある.

2

音速の変化等はとりあえず考えない. また, あくまで線形の範囲に絞る.

3

Marzo, Asier, et al. “Realization of compact tractor beams using acoustic delay-lines.” Applied Physics Letters 110.1 (2017).

4

Marzo, Asier, Tom Corkett, and Bruce W. Drinkwater. “Ultraino: An open phased-array system for narrowband airborne ultrasound transmission.” IEEE transactions on ultrasonics, ferroelectrics, and frequency control 65.1 (2017): 102-111.

5

Price, Adam, and Benjamin Long. “Fibonacci spiral arranged ultrasound phased array for mid-air haptics.” 2018 IEEE International Ultrasonics Symposium (IUS). IEEE, 2018.

フェーズドアレイの線形性

Author: Shun Suzuki

Date: 2024-01-06


ここでは, フェーズドアレイの線形性について考察する. 超音波振動子にはそれぞれ個体差がある. 同じ駆動信号を振動子に印加しても, 個体によって放射される超音波の位相や振幅は異なる. 以下では, この個体差が焦点生成にどのような影響を与えるか考えてみよう.

個の振動子を考え, 全ての振動子に同じ駆動信号を与えたとき振動子が放射する超音波の音圧をとおく. ここで, が正規分布に独立かつ同一に従うと仮定する. また, 放射する超音波の位相についても同様のことを仮定する.

焦点がフェーズドアレイから十分に遠く, かつ, 指向性や減衰係数が全ての振動子で同一であると仮定すると, 焦点における音圧 となる. ここで, 位相の分散が十分に小さいと仮定すると, となる. ここで, は自由度分布に従うので, となる.

すなわち, 焦点の音圧の期待値は振動子の数に対して線形増加する. また, 振動子1個あたりの期待音圧はとなる.

以下に振動子の振幅と位相の個体差を測定したものを載せる. 測定は振動子の直上で行い, マイクの飽和を避けるためデューティー比はとした. この測定を個の振動子に対して行った. 正規分布でフィッティングした結果, 位相の標準偏差はであった. したがって, 振動子の期待音圧は個体差がないと仮定した場合の約倍になると考えられる.

観測された振幅の個体差. サンプル数は2241.
観測された位相の個体差. サンプル数は2241.

位相, 及び, 振幅の必要分解能

Author: Shun Suzuki

Date: 2024-01-06


ここでは, 焦点を生成するのに必要な位相, 及び, 振幅の必要分解能について考察する. AUTD3はどちらもの分解能を持つ. これは実装の都合で決定されたものであり, 理論的な背景があるものではない. 以下では, これらについて考えてみよう.

まず, 位相の分解能について考えてみる. 個の振動子を並べ, アレイの中心から直上500 mmの位置に焦点を生成することを考える.

以下に位相の分解能, すなわち, 振動子の取りうる位相をとした場合の, 焦点の音圧をシミュレートしたものを示す. なお, 焦点音圧は単純な球面波の重ね合わせから計算した.

位相の分解能に対する焦点の音圧. 右下のグラフはからの拡大図.

また, 以下に位相の分解能に対する焦点位置の精度, すなわち, 期待する焦点位置と実際に音圧の最大値を取る点との誤差を示す. ここで, 焦点の位置はアレイ中心を原点とし, とした.

位相の分解能に対する焦点の位置の誤差. 青線は平均, 色つきの範囲は値が存在する範囲を示す. 右下のグラフはからの拡大図.

以上の図に示すように, 16段階, すなわちの分解能があれば, 音圧はほぼ上限に達し, 焦点位置の誤差も以下であることがわかる. したがって, 少なくとも単一焦点生成には位相はで十分である.

一方, 振幅についてどの程度の分解能が必要なのか明確な根拠は現在存在しない. そのため, 現在は経験的に振幅をにしている. また, 実装が楽なので位相もにしている.

なお, 静音化など処理を考えると, 振幅も内部的には十分な分解能を持つことが望ましい. 通信時はで行い, FPGAやマイコンでより細かく分割するなどの方がいいかもしれない. 振幅の必要分解能については将来の研究に任せることにする.

多焦点音場再構成

Author: Shun Suzuki

Date: 2024-01-09


本節では, Holo Gainの実装, すなわち, フェーズドアレーを用いた多焦点の音像の再構成手法について述べる.

なお, 本文章では,

  1. 各振動子の特性は同一
  2. 非線形項は無視できる, すなわち, 音場は個々の振動子が放出したものの重ね合わせになる

という事を仮定する.

定式化

問題の定義は以下となる.

点の焦点における音圧の振幅が与えられたとき, これを出力するような個の振動子の振幅と位相, すなわち, を満たすを求めたい. ただし, 振動子の出力には上限があり, を満たす.」

ここで, は伝達行列であり, 例えば, 障害物のない状態の球面波を仮定すると, である. また, は波数, は振動子の位置である. なお, 振動項はすべての振動子で同一と仮定したので無視する.

Sさらに指向性と空気による吸収項を考慮して とするモデル化もあるが, 本章の議論は本質的にの要素の表記に依らない.

複素数への拡張: 焦点の位相の導入

焦点における複素音圧の位相をとすると, 上式はまとめて と書ける.

一般的に, 超音波フェーズドアレーによる触覚提示や音響浮遊において, この位相は重要でない. 例えば, の超音波で考えると, 2つの焦点が同位相で振動していたとしても, 逆位相 (すなわち, のずれ)で振動していたとしても, ヒトの触覚は区別できない. したがって, に加えて, 焦点の位相にも自由度がある.

以下に, この問題の解法をいくつかの分類に分けて論じていく. なお, この分類は筆者の恣意的なものである.

空間分割スキーム

Author: Shun Suzuki

Date: 2024-09-18


この問題に対する解の1つは, フェーズドアレイを個に空間的に分割して, それぞれのアレイが単一焦点を出すことである. 分割のパターンにはいくつかのバリエーションが考えられる. 以下に二分割の場合の例を載せる.

(a) 左右
(b) 縦縞
(c) 横縞
(d) 市松模様

どのような分割が最適かは, アレイの配置や焦点の位置に依存する. (著者の知る限りでは) ヒューリスティックに決める以外の方法はない. そのため, SDKには実装されていない.

NOTE: Controller::groupGain::groupを用いることで, アレイを分割すること自体は可能.

この手法は焦点の数が増えてきたらどうすべきか, といった問題や, 各焦点同士の干渉などを一切考慮していない, などの問題がある.

逆に, これらが無視できるような状況, 例えば, アレイが十分に大きく, 焦点同士も十分に離れている場合などでは優れた手法となる.

フェーズドアレイ分割法の計算量は, (分割に係る計算量を無視すれば) である.

逆フーリエ変換

Author: Shun Suzuki

Date: 2024-01-09


ここでは, 逆フーリエ変換を用いた方法を示す. これもSDKには実装されていないが1, 参考のために載せておく.

この手法では, フェーズドアレイが2次元平面グリッド上に配置されている場合を考える. また, 焦点もこのアレイに水平な面内に配置されており, アレイ表面から十分離れているとする.

このとき, xy平面上の振動子を添字で区別する. すなわち, とする. また, を満たすと仮定する.

この時, となる. ここで, 最初の近似をFresnel近似, 最後の近似をFraunhofer近似という. さらに, と近似する. これを近軸近似という.

これらの近似を用いると となる.

最後の総和はフェーズドアレイ表面の振幅位相パターンの2次元離散フーリエ変換にほかならない. したがって, となる.

この場合の計算量は, アレイがの正方形だとすると, 2次元FFTを用いてになる.

1

基本的に, この手法で得られる音場が弱い, アレイと目標音場がそれぞれ平面的である必要がある, 目標音場の指定が面倒, といった問題点があり, 実装に見合った利得がないため.

半正定値緩和法

Author: Shun Suzuki

Date: 2024-08-13


NOTE: この手法は現在SDKでは実装されていない.

ここでは, 件の行列方程式を解く方法を述べる. ただし, 行列方程式を解くにはまず焦点の位相を決定する必要がある.

行列方程式を解いて得られた解が振動子出力の制約を満たさないのなら, 出力を一律に抑える必要がある. そのため, 一部の振動子が突出して強いパワーで駆動するような解が得られたとしたら, 上限に合わせて全体をスケールしなければならず, 弱い場が生成されてしまう. このような制約の下で, “良い“解を見つけるために, 焦点における位相を最適化する手法がこれまでにいくつか提案されている.

以下に, 井上らによる半正定値緩和法によるを紹介する1 2 3.

まず, として, この問題を次のような最適化問題に書き換える. ここでは位相成分を表すためなる制約が付く.

ここで, を満たすようなが存在するとすると となる. ここで, はエルミート転置をあらわす.

と置くと, 最適化問題は以下のように書き下せる.

(以下の議論は文献4の2.4. Complex MaxCut.による.) 上記最適化は制約条件により凸問題ではない. そこで, と置くと, となる. ここで, ランク制限を落とすと半正定値問題となり, 効率的に解くことができる. ではない場合はの最大固有値に対応する固有ベクトルがの近似解となる.

ブロック座標降下法を用いてを求めるアルゴリズムは文献4 Algorithm 3を参照5.

あとは, を満たすを求めればいい. このはMoore-Penroseの擬似逆行列と呼ばれていて, の特異値分解から として計算できる. ただし, に対して である.

井上らの方法では, ここにさらにTikhonov正則化が入る6. すなわち, 最小化問題に以下の正則化項を加える. これは, 機械学習の文脈ではリッジ回帰などと呼ばれる, L2正則化である. この正則化には, 解の大きさを抑えるような効果がある. この場合の解は, 以下で与えられる

1

Inoue, Seki, et al. “HORN: the hapt-optic reconstruction.” ACM SIGGRAPH 2014 Emerging Technologies. 2014. 1-1.

2

Inoue, S., et al. “HORN: Stationary airborne ultrasound 3d haptic image.” Asia Haptics (2014): 18-20.

3

Inoue, Seki, Yasutoshi Makino, and Hiroyuki Shinoda. “Active touch perception produced by airborne ultrasonic haptic hologram.” 2015 IEEE World Haptics Conference (WHC). IEEE, 2015.

4

Waldspurger, Irene, Alexandre d’Aspremont, and Stéphane Mallat. “Phase recovery, maxcut and complex semidefinite programming.” Mathematical Programming 149 (2015): 47-81.

5

実際の実装ではの計算において番目の列を取得した後, 番目の行を除くのではなく単にを代入するのが良い.これは, のようにある行と列を除いた行列を取得するのが比較的コストのかかる処理であるため. (少なくとも簡単にベンチマークを取った限りでは正しかった.)

6

論文には具体的な正則化の方法は書かれていない.

固有値問題と行列方程式

Author: Shun Suzuki

Date: 2024-01-09


ここでは, 件の最適化問題の解法の1つとして, Longらによる方法 1を紹介する. 正直, この論文はお世辞にも理解しやすいとは言い難く, この節はおそらくこうだろうという自己解釈を多分に含むので注意されたい.

NOTE: この手法は現在SDKでは実装されていない.

Longらの方法は, 単一焦点解の重ね合わせに基づく. 単一焦点解の重ね合わせとして駆動した場合, 各焦点の位相は何が最適か, を固有値問題として解く.

まずはじめに, 単一焦点を生成する解について考えよう. ある点での複素音圧は であるため, に焦点を生成する解は明らかに である. これは, 伝搬による位相遅れを補償するように, 遠くの振動子が早めに音を出すことを意味する.

これには, 時間対称性を用いたもう1つの解釈がある. (損失のない) 波動方程式は時間に対して対称であるため, 焦点に音源を置いたときの各振動子における音圧を記録し, これを逆再生することで焦点が生成できる. 逆再生するため,焦点から振動子への伝搬行列はと表される. ここで, は複素共役を表す. すなわち, 2とすれば, 逆に焦点が生成されるはずである. これより, となり, 焦点での音圧からずれる. これは, 振動子の配置が離散的であることに起因する. しかし, 今考えているのはフェーズドアレイなので, この分を補正しなくてはいけない.

したがって, 実際の振動子の駆動は とすべきである(論文 1の式(8)).

ここで, 次のような行列を考える, 少しわかりにくいが, 例えば, 第0列はの位置に音圧の焦点を生成しようとしたときの振動子の駆動ベクトルである. ここで, 焦点音圧の位相 から, 位相ベクトルを, とすると, これは焦点に (位相込の) 複素音圧の焦点を生成しようとしたときの振動子の各駆動ベクトルの線形重ね合わせをあらわす. したがって, は, そのような線形重ね合わせ駆動ベクトルが, 実際に焦点に生成する音圧を表す. (行列が論文 1の行列に等しい.) これが, もともとの焦点音圧 (の定数倍) に等しくなってほしいので が解くべき問題になり, これはまさに固有値問題である. 定数倍の自由度は, 最終的に振動子の出力を分の1倍すればいい話なので問題にならない.

さて, 上の固有値問題の解のうち, もっとも望ましいのはのもっとも大きいものである. なぜなら, が大きいなら, その分だけ振動子の出力を抑えることができるからである. これは, 省電力化とアーティファクトの抑制につながる. 以上により, 焦点の位相の最適化が終わる.

焦点の位相が求まったので, 各焦点を生成する駆動ベクトルの重ね合わせである, とすれば良いような気もするが, この部分は振動子の出力可能な最大音圧を一切考慮していない. すでに述べたように, 一部の振動子のみが突出して強い音を出すような解は望ましくない. そこで, 振動子の出力のばらつきを抑えるためにTikhonov正則化を導入し, 最適化問題を次のように拡張したものを解く. ここで, である.

正直に言うと, ここの解釈はいまだにきちんと理解できていない. おおよそは以下のような感じであろう. まず, 番目の振動子が持つ各焦点への“影響力“のようなものを表している. 連立方程式にのような式を追加することにより, 影響力の強い振動子の出力は弱めることにより, 振動子の出力のバランスを良くする. ただし, なぜがこの表記になるのかはわからない.

なお, Longらの論文 1では, 固有値問題や逆問題の具体的な解き方は明記されていない. また, 出力は振動子の限界を超えた場合には切り捨てるという方針を取る.

計算量は, 固有値問題を解くのに, 逆問題を解くのに, 例えばLU分解を使うととなる. したがって, である3.

1

Long, Benjamin, et al. “Rendering volumetric haptic shapes in mid-air using ultrasound.” ACM Transactions on Graphics (TOG) 33.6 (2014): 1-10.

2

焦点の位相はとした.

3

普通は

逆伝搬法

Author: Shun Suzuki

Date: 2024-01-09


この章では, 焦点からの逆伝搬を用いる解法を記す.

ナイーブな方法

ナイーブな方法では, 制御点の位相をすべてとして, 単純に焦点から逆伝搬した解を重ね合わせる. すなわち, である. (実際には, ここからさらに振動子の出力の制約を満たすようにする必要がある.)

しかし, すでに何度も述べたように, 焦点の位相に対しては最適化の余地がある.

Gerchberg-Saxton (GS) 法

まず, Marzoらの提案したフェーズドアレイ用の焦点位相回復について述べる1. 以下が, そのアルゴリズムである.

  1. 初期化:
  2. 以下を回繰り返す:
    1. 順伝搬:
    2. 焦点の正規化:
    3. 逆伝搬:
    4. 振動子の正規化:

このアルゴリズムはの像, すなわち, フェーズドアレイが実際に出力できる音場の集合と, 目標音場の集合との間を交互に射影しながら, その積集合内の一点を求めている. このように, 伝搬逆伝搬を繰り返して, 望みの場に収束させる方法を, Gerchberg-Saxton (GS) 法2やIterative Fourier transform algorithm (IFTA) 3 4 と呼ぶ.

以下にGS法の概念図を示す. GS法は2つの集合が凸包である場合には収束する5. このとき, なら, に, そうでないのなら, 間の距離を最小にする点に収束する6. しかし, 今回は集合は凸ではない. そのため, 収束は保証されないが, 実用的にはほとんど問題ない解が出てくることが確かめられている.

GS法の概念図

なお, このアルゴリズムでは振動子の振幅成分を決定できない. 論文 1では, 振動子の振幅は固定されている().

GS for Phased Arrays of Transducers (GS-PAT)

上記をさらに最適化したアルゴリズムとして, GS-PATが提案されている 7.

この論文の主目的は高速化である. GS法の繰り返し第4ステップがなくなれば, 1,3,4ステップををまとめてとかける, ということから着想を得ている. そこで, 以下の規格化逆伝搬行列を考える.

このの意味は固有値問題と行列方程式の行列とほとんど同じである. このを用いて, GS-PATのアルゴリズムは次のように表される.

  1. 初期化:
  2. 以下を回繰り返す:

第4ステップを避けて, を使うことのメリットは2つある.

  1. はあらかじめ計算でき, その次元はである. ほとんどの場合, であるため, 各ステップの行列計算量は少なくなり, 計算速度の面で有利.
  2. 振動子の振幅の自由度を残したままにできる.
1

Marzo, Asier, and Bruce W. Drinkwater. “Holographic acoustic tweezers.” Proceedings of the National Academy of Sciences 116.1 (2019): 84-89.

2

Gerhberg, R., and W. Saxton. “A practical algorithm for the determination of phase from image and diffraction plane picture.” Optik 35 (1972): 237-246.

3

Wyrowski, Frank, and Olof Bryngdahl. “Iterative Fourier-transform algorithm applied to computer holography.” JOSA A 5.7 (1988): 1058-1065.

4

Wyrowski, Frank. “Iterative quantization of digital amplitude holograms.” Applied optics 28.18 (1989): 3864-3870.

5

Cheney, Ward, and Allen A. Goldstein. “Proximity maps for convex sets.” Proceedings of the American Mathematical Society 10.3 (1959): 448-450.

6

ただし, 有限回のステップで収束することは保証されていない.

7

Plasencia, Diego Martinez, et al. “GS-PAT: high-speed multi-point sound-fields for phased arrays of transducers.” ACM Transactions on Graphics (TOG) 39.4 (2020): 138-1.

非線形最小二乗法

Author: Shun Suzuki

Date: 2024-01-09


件の行列方程式を次の非線形最小二乗問題として解く方法もある, ここでのパラメータはの位相とである. 反復的に上式を最小化していく方法がいくつか知られている.

Levenberg-Marquardt (LM) 法

ここでは, LM (Levenberg-Marquardt) 法 1 2を紹介する. LM法を用いた例としては 3 4などがある.

後のため, 推定すべきパラメータ と置き, 最適化問題を と書く. ここでベクトルに対して を意味する. まず, は複素関数であることに注意する. LM法を適用できるようにするため, 実部と虚部に分けると, となる. ここで, はそれぞれの実部と虚部を表す. とおくと, は実関数であり となるので, LM法が適用できる.

この時, のJacobian から, となる. このを用いて, LM法では, 以下の更新式に従いパラメータを更新する. の決め方にはいくつかのバリエーションが知られている. 例えば, 文献5を参照.

ここで, 計算量を削減するため, をあらかじめ計算しておこう. であるが, であるから, 結局, (はHermite行列なので, 一部省略した. 以下同様.)

ここで, 列ベクトルに対して, であり, したがって, となる. なお, のHadamard積を意味する. ここで, と定義すると, となり, と計算できる.

次に, を計算しよう. ここで, なので, となる. ここで, 列ベクトルに対して, である. したがって, は, の各行を最初の列分だけ足したものに等しい. したがって, ここで, は, の各行を最初の列分だけ足し合わせて得られる列ベクトルである.

なお実際にこのLM法を実装してみるとわかるが, 実は という風に, 振動子の出力を(一律に)固定してしまうほうが, 再現性の意味でも, 計算量の意味でも性能がいい6. このときは, 例えば, パラメータはとして, を計算すれば良い.

勾配降下法とGauss-Newton法

なお, 更新式を としたものはGauss-Newton法と呼ばれ, としたものは勾配降下法 (Gradient-Descent), あるいは, 最急降下法 (Steepest-Descent) と呼ばれる. LM法はこれらの間の子である.

1

Levenberg, Kenneth. “A method for the solution of certain non-linear problems in least squares.” Quarterly of applied mathematics 2.2 (1944): 164-168.

2

Marquardt, Donald W. “An algorithm for least-squares estimation of nonlinear parameters.” Journal of the society for Industrial and Applied Mathematics 11.2 (1963): 431-441.

3

Sakiyama, Emiri, et al. “Midair tactile reproduction of real objects.” International Conference on Human Haptic Sensing and Touch Enabled Computer Applications. Cham: Springer International Publishing, 2020.

4

Matsubayashi, Atsushi, Yasutoshi Makino, and Hiroyuki Shinoda. “Rendering ultrasound pressure distribution on hand surface in real-time.” Haptics: Science, Technology, Applications: 12th International Conference, EuroHaptics 2020, Leiden, The Netherlands, September 6–9, 2020, Proceedings 12. Springer International Publishing, 2020.

5

Madsen, Kaj & Nielsen, Hans & Tingleff, O., “Methods for Non-Linear Least Squares Problems (2nd ed.),” 60, 2004.

6

詳しいことはまだわかっていないが, 出力に対する制約が入っていないことが原因と考えられる.

音響パワー最適化法

Author: Shun Suzuki

Date: 2024-01-09


NOTE: この手法は現在SDKでは実装されていない.

実際の応用上は, 音圧よりも音圧の二乗, すなわちパワーが重要である. そこで, 長谷川らはこれを最適化する手法を提案した1. すなわち, この方法では, 以下の最適化を考える. この場合は, 制御点の位相成分を最適化する必要はない.

さて, 成分のみであり, ほかはである行列をと表すと, である. 論文 1では, Tikhonov正則化を採用し, 以下の目的関数の最適化をBroyden–Fletcher–Goldfarb–Shanno (BFGS) 法2により行っている.

なお, 論文 1では初期値は, としている. ただし, の位相はすべてである.

1

Hasegawa, Keisuke, Hiroyuki Shinoda, and Takaaki Nara. “Volumetric acoustic holography and its application to self-positioning by single channel measurement.” Journal of Applied Physics 127.24 (2020).

2

Fletcher, R. “Practical methods of optimization.” (1987).

貪欲法

Author: Shun Suzuki

Date: 2024-01-09


ここまでの手法は, 振動子の振幅・位相を連続量としていた. しかし, 実際には, フェーズドアレイの駆動は離散量で指定する. しかも, かなり低い量子化深度()で十分である (位相, 及び, 振幅の必要分解能参照).

したがって, 位相を離散化することで, 組合せ最適化問題として解く方法が考えられる.

貪欲法 (Greedy)

組み合わせ最適化問題として解く方法の1つに,貪欲法と全探索を用いた方法がある1.

以下にそのアルゴリズムを示す. 振動子の振幅と位相がのように離散化されていると, このアルゴリズムは

  1. で初期化
  2. まで以下を繰り返す
    1. まで以下を繰り返す
      • まで以下を繰り返す
        • を計算する.
    2. としてセットする. ここで, を満たす.

である. このアルゴリズムでは, まず, 1つの振動子を駆動する. そして, 全ての可能な振幅位相を探索した後, 最適なものを選ぶ. 次に, この振動子の出力は固定して, 新たな振動子を追加し, この新たな振動子に対して全ての可能な振幅位相を探索した後, 最適なものを選んで固定する. これを全ての振動子に対して繰り返していく. なお, 振動子の選択はランダムに行うべきである1.

ここで評価関数番目から番目までの振動子を駆動した際の, 目標音圧と実際に生成される音圧との二乗誤差であり である. なお, であるため, をキャッシュ化しておくことで, 評価関数の計算はで行える. したがって, このアルゴリズムは明らかにで実行できる.

この手法のメリットは, 焦点の位相を最適化する必要がないことである. また, 計算速度的にも有利である.

焦点位相に対する貪欲法 (LSSGreedy)

上記のアルゴリズムでは振動子の振幅/位相を離散化したが, 焦点の位相を離散化し, 単一焦点の重ね合わせ解において最適な焦点の位相を貪欲法と全探索で求める方法もある2. なお, 現状このアルゴリズムを使用するメリットは特になく, Greedyのほうが基本的に性能は良いのでSDKには実装されていない.

1

Suzuki, Shun, et al. “Radiation pressure field reconstruction for ultrasound midair haptics by Greedy algorithm with brute-force search.” IEEE Transactions on Haptics 14.4 (2021): 914-921.

2

Chen, Jianyu, et al. “Sound Pressure Field Reconstruction for Ultrasound Phased Array by Linear Synthesis Scheme Optimization.” International Conference on Human Haptic Sensing and Touch Enabled Computer Applications. Cham: Springer International Publishing, 2022.

複数周波数の応用

Author: Shun Suzuki

Date: 2024-05-24


最新バージョンのAUTD3ファームウェア/ソフトウェアでは, 超音波周波数の変更は不可能になった. 以下はメモのために残している.

時空間変調

ここでは, 周波数のみ異なる2つのアレイが同じ位置に焦点を生成したときの焦点の音圧について考察する.

焦点付近の音場はほぼ, アレイの中心から焦点までのベクトルに平行に進む平面波で近似できるとする. すなわち, 焦点付近の音場 のように書くことができる.

さて, 周波数の異なる2つのアレイがx軸に対して対称に置かれている, すなわち, アレイの中心がで, それぞれの周波数がであるとする. このとき, に焦点を生成する場合を考えよう.

波数と音速の関係式はなので, それぞれのアレイの波数ベクトルとなる, 振動子の音圧が周波数に依存しないとすると, 焦点近傍の音圧は となる. したがって, が最大となる点は であり, この点の速度は となる. すなわち, 位相変化を行うことなく最大音圧点を移動させることができる.

音響放射圧

Author: Shun Suzuki

Date: 2024-01-06


一般的に, 音波はそれが伝搬する媒質に置かれた物体に定常的な力を与える. これを放射圧 (Radiation Pressure) と呼ぶ. 一般的に, 放射圧はRayleigh放射圧とLangevin放射圧に分類される. 前者はある容器内に閉じ込められた音波が壁にもたらす放射圧である. 一方, 後者は自由空間に置かれた物体に作用する放射圧である. 例えば, 音響浮遊や触覚提示では後者の状況を考えるので, 本稿は後者の放射圧のみ扱うこととする.

音響放射圧の説明には, 平均エネルギー密度を用いたものがあるが, ここでは流体力学の観点から説明する. 平均エネルギー密度による説明は文献1などに詳しい (なお, 平均エネルギー密度による説明は, 音場が平面波に分解できるときのみ成り立つことが指摘されている2). 本文章の議論は3, 4, 5に基づく. 一部, 個人的な解釈を入れるので, 原論文と読み比べながら読むことを勧める.

圧力を, 密度を, 粒子速度をとすると, オイラー方程式6は以下のように表される. また, 連続方程式は と表される. さらに, 音波の伝播は熱の伝搬より圧倒的に早く, 断熱的であると仮定する7. したがって, 熱力学の結果より, 音圧は密度のみの関数となり8, と表される. ここで, は速度の次元を持つ, の関数である. 例えば, 理想気体の場合は断熱方程式 から となる. ここでは比熱比であり, 添字は平衡状態を表す.

一般的な流体の場合は, の周りでテイラー展開を行い と表記する.

まず, 粒子速度の大きさとの比をとする, このを音響マッハ数と呼ぶ. は速度の次元を持つので音響マッハ数の次元は無次元である.

ここで, 音響マッハ数がであると仮定し9, を摂動展開する. すなわち と表す10. この時, は, でとなる.

これらを, オイラー方程式と連続方程式に代入すると, 及び, となる.

これより, まず0次の式として, を得る. ここで, なのでが定数であることがわかる. したがって, 必然も定数になる. これらは, を平衡状態とした仮定に矛盾しない.

次に一次の式として, を得る. 上2式より, を得る. これらは所謂波動方程式であり, が線形の波動現象に従うことを意味する.

最後に, 2次の式として, を得る.

さて, 式(1), (2)を音圧の表示にすると, となる.

ここで, 渦なしの場を仮定し, 次の速度ポテンシャルを導入する. すると, 式(3), (4)はそれぞれ となり, を得る.

したがって, ある物体にかかる音響放射力 (物体にかかる力の時間平均) は, 音圧と流入運動量の2次までの項を考えると, となる11.

まず, 一次の項は, 線形の波動方程式に従うので, となる. また, 定常状態では, が周期的であるので, となり, は定数となる. 閉じた領域で積分すれば, これも消滅し, となる.

一般の場合, ある物体にかかる音響放射力は上記の式で与えられる. しかし, これを計算するのは容易ではない. まず, 物体表面が音響放射圧それ自体などにより変動する可能性がある. また, 速度ポテンシャルや (一次の) 音圧などは反射波のそれを含む. 一般の場合にこれらを求めるのは難しい.

無限剛壁と平面波の場合

ここでは, に置かれた無限剛壁に平面波が入射する場合を考えてみよう.

音響放射力の剛壁に垂直な方向の成分は, となる. この被被積分項を音響放射圧と呼ぶ12.

波が角度で壁に入射するとし とする. ここで, は反射率で, は反射波の位相である.

この時, より, となる.

なお, なので, と表すことができる. ここで, は入射波の音圧の二乗平均である.

1

実吉純一, “超音波技術便覧”, 日刊工業新聞社, 1978.

2

長谷川高陽, “ランジュバン放射圧に関する統一理論”, 日本音響学会誌, 1996.

3

Eckart, Carl. “Vortices and streams caused by sound waves.” Physical review 73.1 (1948): 68.

4

Awatani, Jobu. “Studies on acoustic radiation pressure. I.(General considerations).” The Journal of the Acoustical Society of America 27.2 (1955): 278-281.

5

Hasegawa, Takahi, et al. “A general theory of Rayleigh and Langevin radiation pressures.” Acoustical Science and Technology 21.3 (2000): 145-152.

6

粘性なしのナビエ・ストークス方程式. また, ここでは外力場も考えていない.

7

別の言い方をすると等エントロピーを仮定する.

8

このような流体をバロトロピー流体 (barotropic fluid) と呼ぶ.

9

この仮定の妥当性は後に示される.

10

なので, は存在しない.

11

は大気圧にあたり, これによる力は音響放射力とは呼ばない. なお, 定数なのでそもそも閉じた領域で積分すれば消滅する.

12

方向によるので正確には圧力ではない.

Document History

DateDescription
2025/03/26Version 32.0.1 初版
2025/03/25Version 32.0.0 初版
2025/03/07Version 31.0.1 初版
2025/02/24Version 30.0.1 初版
2025/02/10Version 29.0.0 初版
2024/10/15Version 28.1.0 初版
2024/10/14Version 28.0.1 初版
2024/10/12Version 28.0.0 初版
2024/08/09Version 27.0.0 初版
2024/06/29Version 26.0.0 初版
2024/06/18Version 25.3.2 初版
2024/06/17Version 25.3.1 初版
2024/06/14Version 25.2.3 初版
2024/06/10Version 25.1.0 初版
2024/06/08Version 25.0.1 初版
2024/06/06Version 25.0.0 初版
2024/05/22Version 24.1.0 初版
2024/05/18Version 24.0.0 初版
2024/05/13Version 23.1.0 初版
2024/05/11Version 23.0.1 初版
2024/05/11Version 23.0.0 初版
2024/04/08Version 22.1.0 初版
2024/03/30Version 22.0.4 初版
2024/02/25Version 22.0.1 初版
2024/01/29Version 21.1.0 初版
2024/01/26Version 21.0.1 初版
2024/01/11Version 20.0.3 初版
2024/01/05Version 20.0.0 初版
2023/12/14Version 19.1.0 初版
2023/12/10Version 19.0.0 初版
2023/12/04Version 18.0.1 初版
2023/12/02Version 18.0.0 初版
2023/11/29Version 17.0.3 初版
2023/11/28Version 17.0.2 初版
2023/10/14Version 16.0.0 初版
2023/10/04Version 15.3.1 初版
2023/10/04Version 15.3.0 初版
2023/09/29Version 15.2.1 初版
2023/09/28Version 15.2.0 初版
2023/09/22Version 15.1.2 初版
2023/09/15Version 15.1.1 初版
2023/09/14Version 15.1.0 初版
2023/09/14Version 15.0.2 初版
2023/08/07Version 14.2.2 初版
2023/08/01Version 14.2.1 初版
2023/07/28Version 14.2.0 初版
2023/07/27Version 14.1.0 初版
2023/07/19Version 14.0.1 初版
2023/07/19Version 14.0.0 初版
2023/07/11Version 13.0.0 初版
2023/07/04Version 12.3.1 初版
2023/07/04Version 12.3.0 初版
2023/06/24Version 12.2.0 初版
2023/06/23Version 12.1.1 初版
2023/06/22Version 12.1.0 初版
2023/06/21Version 12.0.0 初版
2023/06/12Version 11.1.0 初版
2023/05/01Version 9.0.1 初版
2023/04/29Version 9.0.0 初版
2023/04/26Version 8.5.0 初版
2023/04/23Version 8.4.1 初版
2023/04/18Version 8.4.0 初版
2023/03/21Version 8.3.0 初版
2023/03/10Version 8.2.0 初版
2023/02/19Version 8.1.2 初版
2023/02/03Version 8.1.1 初版
2023/02/02Version 8.1.0 初版
2023/01/24Version 2.8.0 初版
2023/01/18Version 2.7.6 初版
2023/01/17Version 2.7.5 初版
2023/01/12Version 2.7.4 初版
2022/12/29Version 2.7.2 初版
2022/12/24Version 2.7.1 初版
2022/12/16Version 2.7.0 初版
2022/12/13Version 2.6.8 初版
2022/12/10Version 2.6.7 初版
2022/12/06Version 2.6.6 初版
2022/11/28Version 2.6.5 初版
2022/11/22Version 2.6.4 初版
2022/11/21Version 2.6.3 初版
2022/11/15Version 2.6.2 初版
2022/11/13Version 2.6.1 初版
2022/11/10Version 2.6.0 初版
2022/11/08Version 2.5.2 初版
2022/11/06Version 2.5.1 初版
2022/11/04Version 2.5.0 初版
2022/10/25Version 2.4.5 初版
2022/10/21Version 2.4.4 初版
2022/10/18Version 2.4.3 初版
2022/10/14Version 2.4.2 初版
2022/10/09Version 2.4.1 初版
2022/09/28Version 2.4.0 初版
2022/08/14Version 2.3.1 初版
2022/08/08Version 2.3.0 初版
2022/07/28Version 2.2.2 FAQ, 及び, TwinCATに付いて追記
2022/06/29Version 2.2.2 初版
2022/06/22Version 2.2.1 初版
2022/06/10Version 2.2.0 初版
2022/06/02Version 2.1.0 初版
2022/05/25Version 2.0.3 初版
2022/05/24Version 2.0.2 初版
2022/05/23FFI/python, FFI/csharp, migration_guideを追加
2022/05/22Version 2.0.1 初版
2022/05/17Version 2.0.0 初版