About AUTD3
AUTD3 is an airborne ultrasound phased array device for midair haptics. A phased array is an array of ultrasound transducers that can control the phase of each transducer individually. By controlling the phase of the ultrasound, an arbitrary sound field can be generated in space.
The energy of sound waves that are well focused using a phased array produces acoustic radiation pressure. This pressure can be used to press the surface of the human body without any contact. The position of the focused focal point can be freely controlled by electronically controlling the phased array. Moreover, by solving the inverse problem, not only a single focal point but also a more complicated spatial distribution of sound pressure can be created.
The upper limit of the magnitude of pressure that can be generated by the phased array is currently about . Also, the spatial resolution is limited to about the wavelength (for example, about for ). Despite these limitations, phased arrays have attracted attention as a technology that allows us to freely design the spatio-temporal distribution of force and create various tactile sensations.
We call this field of non-contact tactile stimulation technology Midair Haptics, and we call this phased array device Airborne Ultrasound Tactile Display (AUTD). The essential part of AUTD was proposed and established by the University of Tokyo between 20081 and the early 2010s2. Since then, universities and companies from various countries have entered into this field, and active research and development has been conducted. AUTD3 is the third version of AUTD developed in Shinoda-Makino Laboratory of the University of Tokyo.
A list of research using AUTD can be found at our laboratory's homepage. Please also refer to this page.
This manual describes the autd3 software library for operating AUTD3.
User's Manual
In the following, we describe the basic usage of the library.
Concept
The following is a basic components of AUTD3 SDK.
Controller
- Controller class. All operations to AUTD3 are done via this classGeometry
- Container ofDevice
Device
- Class corresponding to AUTD3 device
Link
- Interface to AUTD3 devicesGain
- Manage the phase/amplitude of each transducerModulation
- Manage the amplitude modulation (AM) of each transducerSTM
- Manage the spatio-temporal modulation (STM) on firmware
The following is the front and back photos of AUTD3.
AUTD3 is composed of 249 transducers per device1. From SDK, the phase/amplitude of all transducers can be specified individually. The coordinate system of AUTD3 adopts the right-handed coordinate system, and the center of the 0th transducer is the origin. The x-axis is the long axis direction, that is, the direction of 0→17, and the y-axis is the direction of 0→18. In addition, the unit system is mm for distance, rad for angle, and Hz for frequency. The transducers are arranged at intervals of , and the size including the substrate is .
The followings is the dimension of transducer array.
In addition, AUTD3 can be connected to each other via the daisy chain. You can compose extended array by connecting the EherCAT In of the -th device and the EherCAT Out of the -th device with an Ethernet cable. (The ethernet cable must be CAT 5e or higher.)
You have to supply DC power to AUTD3. The power line can be connected to each other, and any of the three power connectors can be used. The power connector is Molex 5566-02A.
Note: AUTD3 consumes up to of current per device. Please pay attention to the maximum output current of the power supply.
transducers are mounted on the substrate, but 3 transducers are missing for the screw holes. The reason why the screw holes are placed at this position is to minimize the gap when multiple devices are placed side by side.
Tutorial
Installation of dependencies
This tutorial uses SOEM. If you are using Windows, install Npcap in WinPcap API-compatible Mode.
Setup devices
First, set up the devices. Here, we assume that only one AUTD3 is used. Connect the PC's Ethernet port and the AUTD3 device's EtherCAT In (Concept) with an Ethernet cable. Next, connect the power supply.
Firmware update
If the firmware is old, normal operation is not guaranteed. The firmware version in this document is assumed to be v10.0.1.
To update the firmware, you need a Windows 10/11 64bit PC with Vivado and J-Link Software installed.
Note: If you only want to update the firmware, we strongly recommend using "Vivado Lab Edition". The "Vivado ML Edition" requires more than 60 GB of disk space to install, while the "Vivado Lab Edition" requires only about 6 GB.
First, connect the AUTD3 device and the PC with XILINX Platform Cable and J-Link Plus with J-Link 9-Pin Cortex-M Adapter, and turn on the power of the AUTD3.
Then, run autd_firmware_writer.ps1
in autd3-firmware on powershell.
Language-specific tutorials
Rust tutorial
First, make a new project and add autd3
and autd3-link-soem
libraries as dependencies.
cargo new --bin autd3-sample
cd autd3-sample
cargo add autd3@29.0.0-rc.16
cargo add autd3-link-soem@29.0.0-rc.16
cargo add tokio --features full
Next, edit src/main.rs
file as follows.
This is the source code for generating a focus with AM modulation.
use autd3::prelude::*;
use autd3_link_soem::{Status, SOEM};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Make a controller to control AUTD
// Configure the devices
// The argument of AUTD3::new is position
// Here, the device is placed at the origin
let mut autd = Controller::builder([AUTD3::new(Point3::origin())])
// Open controller with SOEM link
// The callback specified by with_err_handler is called when error occurs
.open(SOEM::builder().with_err_handler(|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);
}
}))?;
// Check firmware version
// This code assumes that the version is 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 center = autd.center() + Vector3::new(0., 0., 150.0 * mm);
let g = Focus::new(center);
// 150Hz sine wave modulation
let m = Sine::new(150 * Hz);
// 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(())
}
Then, run the program.
cargo run --release
For Linux, macOS users
You may need to run with administrator privileges when using SOEM on Linux or macOS.
cargo build --release && sudo ./target/release/autd3_sample
C++ tutorial
Install dependencies
In this tutorial, we use CMake to build the program.
Build first program
First, open a terminal and prepare a directory for the sample.
mkdir autd3-sample
cd autd3-sample
Then, make CMakeLists.txt
and main.cpp
files.
└─autd3-sample
CMakeLists.txt
main.cpp
Next, edit CMakeLists.txt
as follows,
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)
if(${CMAKE_SYSTEM_PROCESSOR} MATCHES "AMD64")
FetchContent_Declare(
autd3
URL https://github.com/shinolab/autd3-cpp/releases/download/v29.0.0-rc.16/autd3-v29.0.0-rc.16-win-x64.zip
)
FetchContent_Declare(
autd3-link-soem
URL https://github.com/shinolab/autd3-cpp-link-soem/releases/download/v29.0.0-rc.16/autd3-link-soem-v29.0.0-rc.16-win-x64.zip
)
elseif(${CMAKE_SYSTEM_PROCESSOR} MATCHES "ARM64")
FetchContent_Declare(
autd3
URL https://github.com/shinolab/autd3-cpp/releases/download/v29.0.0-rc.16/autd3-v29.0.0-rc.16-win-arm.zip
)
FetchContent_Declare(
autd3-link-soem
URL https://github.com/shinolab/autd3-cpp-link-soem/releases/download/v29.0.0-rc.16/autd3-link-soem-v29.0.0-rc.16-win-arm.zip
)
else()
message(FATAL_ERROR "Unsupported platform: ${CMAKE_SYSTEM_PROCESSOR}")
endif()
elseif(APPLE)
FetchContent_Declare(
autd3
URL https://github.com/shinolab/autd3-cpp/releases/download/v29.0.0-rc.16/autd3-v29.0.0-rc.16-macos-aarch64.tar.gz
)
FetchContent_Declare(
autd3-link-soem
URL https://github.com/shinolab/autd3-cpp-link-soem/releases/download/v29.0.0-rc.16/autd3-link-soem-v29.0.0-rc.16-macos-aarch64.tar.gz
)
else()
FetchContent_Declare(
autd3
URL https://github.com/shinolab/autd3-cpp/releases/download/v29.0.0-rc.16/autd3-v29.0.0-rc.16-linux-x64.tar.gz
)
FetchContent_Declare(
autd3-link-soem
URL https://github.com/shinolab/autd3-cpp-link-soem/releases/download/v29.0.0-rc.16/autd3-link-soem-v29.0.0-rc.16-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)
if (CMAKE_GENERATOR MATCHES "Visual Studio")
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT main)
endif()
And, edit main.cpp
as follows.
This is the source code for generating a focus with AM modulation.
#include <autd3_link_soem.hpp>
#include <iostream>
#include "autd3.hpp"
int main() try {
auto autd =
autd3::ControllerBuilder({autd3::AUTD3(autd3::Point3::origin())})
.open(autd3::link::SOEM::builder().with_err_handler(
[](const uint16_t slave, const autd3::link::Status status) {
std::cout << "slave [" << slave << "]: " << status << std::endl;
if (status == autd3::link::Status::Lost()) exit(-1);
}));
const auto firm_version = autd.firmware_version();
std::copy(firm_version.begin(), firm_version.end(),
std::ostream_iterator<autd3::FirmwareVersion>(std::cout, "\n"));
autd.send(autd3::Silencer());
const autd3::Point3 focus = autd.center() + autd3::Vector3(0, 0, 150);
autd3::gain::Focus g(focus);
autd3::modulation::Sine m(150 * autd3::Hz);
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;
}
Then, build with CMake.
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
cmake --build . --config Release
Finally, run the program.
.\Release\main.exe
sudo ./main
C# tutorial
First, open a terminal and prepare a directory for the sample. The, install AUTD3Sharp library.
dotnet new console --name autd3-sample
cd autd3-sample
dotnet add package AUTD3Sharp --version 29.0.0-rc.16
dotnet add package AUTD3Sharp.Link.SOEM --version 29.0.0-rc.16
Next, make Program.cs
file.
This is the source code for generating a focus with AM modulation.
using AUTD3Sharp;
using AUTD3Sharp.Utils;
using AUTD3Sharp.Link;
using AUTD3Sharp.Gain;
using AUTD3Sharp.Modulation;
using static AUTD3Sharp.Units;
using var autd = Controller.Builder([new AUTD3(Point3.Origin)])
.Open(SOEM.Builder().WithErrHandler((slave, status) =>
{
Console.Error.WriteLine($"slave [{slave}]: {status}");
if (status == Status.Lost)
Environment.Exit(-1);
}));
var firmList = autd.FirmwareVersion();
foreach (var firm in firmList)
Console.WriteLine(firm);
autd.Send(new Silencer());
var g = new Focus(autd.Center + new Vector3(0, 0, 150));
var m = new Sine(150u * Hz);
autd.Send((m, g));
Console.ReadKey(true);
autd.Close();
Then, run the program.
dotnet run -c:Release
For Linux, macOS users
You may need to run with administrator privileges when using SOEM on Linux or macOS.
sudo dotnet run -c:Release
Unity tutorial
Unity library use left-handed coordinate system and meter as a basic unit of length.
Installation
You can install the library via Unity Package Manager.
- From the menu bar, select "Edit" > "Project settings" > "Package Manager".
- Click "+" button in upper left corner, then "Add package from git URL".
- Enter
https://github.com/shinolab/AUTD3Sharp.git#upm/latest
and click "Add".- If you need a specific version, specify
#upm/vX.X.X
.
- If you need a specific version, specify
- If you want to use
AUTD3Sharp.Link.SOEM
, addhttps://github.com/shinolab/AUTD3Sharp.Link.SOEM.git#upm/latest
as well.
Sample
Please import Samples from Package Manager.
Python tutorial
First, install pyautd3
library.
pip install pyautd3==29.0.0rc16
pip install pyautd3_link_soem==29.0.0rc16
Next, make main.py
file as follows.
This is the source code for generating a focus with AM modulation.
import os
import numpy as np
from pyautd3 import AUTD3, Controller, Focus, Hz, Silencer, Sine
from pyautd3_link_soem import SOEM, Status
def err_handler(slave: int, status: Status) -> None:
print(f"slave [{slave}]: {status}")
if status == Status.Lost():
# You can also wait for the link to recover, without exiting the process
os._exit(-1)
if __name__ == "__main__":
with Controller.builder([AUTD3([0.0, 0.0, 0.0])]).open(
SOEM.builder().with_err_handler(err_handler),
) 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(autd.center + np.array([0.0, 0.0, 150.0]))
m = Sine(150 * Hz)
autd.send((m, g))
_ = input()
autd.close()
Then, run the program.
python main.py
For linux users
You may need to run with administrator privileges when using SOEM on Linux.
sudo setcap cap_net_raw,cap_net_admin=eip <your python path>
python main.py
For macOS users
You may need to run with administrator privileges when using SOEM on macOS.
sudo chmod +r /dev/bpf*
python main.py
Versioning
AUTD3 follows Semantic Versioning from v8.2.0.
Firmware version
The firmware version also follows semantic versioning.
However, the firmware version is different from the software version.
Geometry
In this chapter, we explain about Geometry
.
Geometry
manages how AUTD3 devices are placed in the real world.
Connect multiple devices
AUTD3 devices can be connected to each other via the daisy chain. SDK is designed to be used transparently even if multiple devices are connected.
To use multiple devices, connect the PC and the EtherCAT In of the first device with an Ethernet cable, and connect the EtherCAT Out of the -th device and the EtherCAT In of the -th device with an Ethernet cable (See Concept).
In SDK, you must call add_device
function in the order of the connected devices when using multiple devices.
For example, suppose you have two devices as shown in the figure above. The left device is the first device, and the right device is the second device. Then, the code is as follows.
use autd3::prelude::*;
#[allow(unused_variables)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let _autd =
Controller::builder([
AUTD3::new(Point3::origin()),
AUTD3::new(Point3::new(AUTD3::DEVICE_WIDTH, 0., 0.))
])
.open(autd3::link::Nop::builder())?;
Ok(())
}
#include<chrono>
#include<autd3.hpp>
#include<autd3/link/nop.hpp>
int main() {
autd3::ControllerBuilder({autd3::AUTD3(autd3::Point3::origin()),
autd3::AUTD3(autd3::Point3(autd3::AUTD3::DEVICE_WIDTH,
0, 0))})
;
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Link;
using AUTD3Sharp.Utils;
Controller.Builder([
new AUTD3(Point3.Origin),
new AUTD3(new Point3(AUTD3.DeviceWidth, 0, 0))
])
;
from pyautd3 import AUTD3, Controller
Controller.builder(
[AUTD3([0.0, 0.0, 0.0]), AUTD3([AUTD3.DEVICE_WIDTH, 0.0, 0.0])],
)
Here, the first argument of the AUTD3
constructor is the position, and the second argument is the rotation.
The rotation is specified by ZYZ Euler angles or quaternions.
Also, AUTD3::DEVICE_WIDTH
is the width of the device (including the outline of the board).
In this example, no rotation is performed, so the second argument can be zero.
And, for example, suppose you have two devices as shown in the figure above, where the global origin is set to the left device. Then, the code is as follows.
use autd3::prelude::*;
#[allow(unused_variables)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let _autd =
Controller::builder([
AUTD3::new(Point3::new(-AUTD3::DEVICE_WIDTH, 0., 0.)),
AUTD3::new(Point3::origin())
])
.open(autd3::link::Nop::builder())?;
Ok(())
}
#include<chrono>
#include<autd3.hpp>
#include<autd3/link/nop.hpp>
int main() {
autd3::ControllerBuilder(
{autd3::AUTD3(autd3::Point3(-autd3::AUTD3::DEVICE_WIDTH, 0, 0)),
autd3::AUTD3(autd3::Point3::origin())})
;
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Link;
using AUTD3Sharp.Utils;
Controller.Builder([
new AUTD3(new Point3(-AUTD3.DeviceWidth, 0, 0)),
new AUTD3(Point3.Origin)
])
;
from pyautd3 import AUTD3, Controller
Controller.builder(
[
AUTD3([-AUTD3.DEVICE_WIDTH, 0.0, 0.0]),
AUTD3([0.0, 0.0, 0.0]),
],
)
Furthermore, for example, suppose you have two devices as shown in the figure above, where the global origin is set to the lower device. Then, the code is as follows.
use autd3::prelude::*;
#[allow(unused_variables)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let _autd =
Controller::builder([
AUTD3::new(Point3::origin()),
AUTD3::new(Point3::new(0., 0., AUTD3::DEVICE_WIDTH))
.with_rotation(EulerAngle::ZYZ(0. * rad, PI/2.0 * rad, 0. * rad))
])
.open(autd3::link::Nop::builder())?;
Ok(())
}
#include<chrono>
#include<autd3.hpp>
#include<autd3/link/nop.hpp>
int main() {
using autd3::pi;
using autd3::rad;
autd3::ControllerBuilder(
{autd3::AUTD3(autd3::Point3::origin()),
autd3::AUTD3(autd3::Point3(autd3::AUTD3::DEVICE_WIDTH, 0, 0))
.with_rotation(autd3::EulerAngles::ZYZ(0 * rad, pi / 2 * rad,
0 * rad))})
;
return 0; }
using System;
using AUTD3Sharp;
using AUTD3Sharp.Utils;
using static AUTD3Sharp.Units;
Controller.Builder([
new AUTD3(Point3.Origin),
new AUTD3(new Point3(AUTD3.DeviceWidth, 0, 0))
.WithRotation(EulerAngles.Zyz(0 * rad, MathF.PI / 2 * rad, 0 * rad))
])
;
import numpy as np
from pyautd3 import AUTD3, Controller, EulerAngles, rad
Controller.builder(
[
AUTD3([0.0, 0.0, 0.0]),
AUTD3([AUTD3.DEVICE_WIDTH, 0.0, 0.0]).with_rotation(
EulerAngles.ZYZ(0 * rad, np.pi / 2 * rad, 0 * rad),
),
],
)
Device/Transducer index
Devices are assigned indices starting from 0 in the order in which they are connected to the PC.
Also, each device has 249 transducers, and local indices are assigned (see the concept for the surface photo of AUTD).
Geometry API
num_devices
: Get the number of devicesnum_transducers
: Get the number of all transducerscenter
: Get the center of all transducers
use autd3::prelude::*;
#[allow(unused_variables)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let autd = Controller::builder([AUTD3::new(Point3::origin())])
.open(autd3::link::Nop::builder())?;
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() {
auto autd =
autd3::ControllerBuilder({autd3::AUTD3(autd3::Point3::origin())}).open(autd3::link::Nop::builder());
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.Builder([new AUTD3(Point3.Origin)]).Open(Nop.Builder());
var numDevices = autd.NumDevices;
var numTransducers = autd.NumTransducers;
var center = autd.Center;
from pyautd3 import AUTD3, Controller
from pyautd3.link.audit import Audit
autd = Controller.builder([AUTD3([0.0, 0.0, 0.0])]).open(Audit.builder())
num_devices = autd.num_devices
num_transducers = autd.num_transducers
center = autd.center
Device access
Geometry
is a container of Device
.
To access Device
, use indexer.
Or, you can use an iterator.
use autd3::prelude::*;
#[allow(unused_variables)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let autd = Controller::builder([AUTD3::new(Point3::origin())])
.open(autd3::link::Nop::builder())?;
let dev = &autd[0];
for dev in &autd {
// do something
}
Ok(())
}
#include<chrono>
#include<autd3.hpp>
#include<autd3/link/nop.hpp>
int main() {
auto autd =
autd3::ControllerBuilder({autd3::AUTD3(autd3::Point3::origin())}).open(autd3::link::Nop::builder());
{
auto dev = autd[0];
}
{
for (auto& dev : autd) {
// do something
}
}
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Link;
using AUTD3Sharp.Utils;
using var autd = Controller.Builder([new AUTD3(Point3.Origin)]).Open(Nop.Builder());
{
var dev = autd[0];
}
foreach (var dev in autd)
{
// do something
}
from pyautd3 import AUTD3, Controller
from pyautd3.link.audit import Audit
autd = Controller.builder([AUTD3([0.0, 0.0, 0.0])]).open(Audit.builder())
dev = autd[0]
for _dev in autd:
pass
Device API
idx
: Index of the deviceenable
: Enable flag. If it is off, the data of the device will not be updated.- Note that this only controls whether the data is updated or not, and does not stop the output.
sound_speed
: Get/set the speed of sound. The unit is mm/s.set_sound_speed_from_temp
: Set the sound speed from the temperature. The unit of the temperature is Celsius. The default sound speed is , which corresponds to the sound speed of air at about 15 degrees Celsius. Note that there is a function with the same name inGeometry
, and you can set the sound speed from the temperature for all devices by using it.attenuation
: Get/set the attenuation coefficient. The unit is Np/mm.translate
: Apply translationrotate
: Apply rotationaffine
: Apply affine transformation (translation/rotation)
use autd3::prelude::*;
#[allow(unused_variables)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut autd = Controller::builder([AUTD3::new(Point3::origin())])
.open(autd3::link::Nop::builder())?;
let dev = &mut autd[0];
let idx = dev.idx();
dev.enable = false;
dev.sound_speed = 340e3;
dev.set_sound_speed_from_temp(15.);
let t = Vector3::new(1., 0., 0.);
let r = UnitQuaternion::from_quaternion(Quaternion::new(1., 0., 0., 0.));
dev.translate(t);
dev.rotate(r);
dev.affine(t, r);
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() {
auto autd =
autd3::ControllerBuilder({autd3::AUTD3(autd3::Point3::origin())}).open(autd3::link::Nop::builder());
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 t = autd3::Vector3(1., 0., 0.);
const auto r = autd3::Quaternion(1., 0., 0., 0.);
dev.translate(t);
dev.rotate(r);
dev.affine(t, r);
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.Builder([new AUTD3(Point3.Origin)]).Open(Nop.Builder());
var dev = autd[0];
var idx = dev.Idx;
dev.Enable = false;
dev.SoundSpeed = 340e3f;
dev.SetSoundSpeedFromTemp(15);
var t = new Vector3(1, 0, 0);
var r = new Quaternion(0, 0, 0, 1);
dev.Translate(t);
dev.Rotate(r);
dev.Affine(t, r);
var wavelength = dev.Wavelength;
var wavenumber = dev.Wavenumber;
var rotation = dev.Rotation;
var xDir = dev.XDirection;
var yDir = dev.YDirection;
var axialDir = dev.AxialDirection;
import numpy as np
from pyautd3 import AUTD3, Controller
from pyautd3.link.audit import Audit
autd = Controller.builder([AUTD3([0.0, 0.0, 0.0])]).open(Audit.builder())
dev = autd[0]
idx = dev.idx
dev.enable = False
dev.sound_speed = 340e3
dev.set_sound_speed_from_temp(15.0)
t = np.array([1.0, 0.0, 0.0])
r = np.array([1.0, 0.0, 0.0, 0.0])
dev.translate(t)
dev.rotate(r)
dev.affine(t, r)
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 access
Device
is a container of Transducer
, and Transducer
contains information of each transducer.
To access Transducer
, use the indexer.
Or, you can use an iterator.
use autd3::prelude::*;
#[allow(unused_variables)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let autd = Controller::builder([AUTD3::new(Point3::origin())])
.open(autd3::link::Nop::builder())?;
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() {
auto autd =
autd3::ControllerBuilder({autd3::AUTD3(autd3::Point3::origin())}).open(autd3::link::Nop::builder());
{
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.Builder([new AUTD3(Point3.Origin)]).Open(Nop.Builder());
{
var tr = autd[0][0];
}
foreach (var tr in autd[0])
{
// do something
}
from pyautd3 import AUTD3, Controller
from pyautd3.link.audit import Audit
autd = Controller.builder([AUTD3([0.0, 0.0, 0.0])]).open(Audit.builder())
tr = autd[0][0]
for _tr in autd[0]:
pass
Transducer API
Following methods are available for Transducer
.
idx
: Get the local index of the transducer.position
: Get the position of the transducer.rotation
: Get the rotation of the transducer. The rotation is represented by a quaternion.x_direction
: Get the x direction vector of the transducer.y_direction
: Get the y direction vector of the transducer.z_direction
: Get the z direction vector of the transducer.wavelength
: Get the wavelength of the transducer. You need to pass the sound speed as an argument.wavenumber
: Get the wavenumber of the transducer. You need to pass the sound speed as an argument.
use autd3::prelude::*;
#[allow(unused_variables)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let autd = Controller::builder([AUTD3::new(Point3::origin())])
.open(autd3::link::Nop::builder())?;
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() {
auto autd =
autd3::ControllerBuilder({autd3::AUTD3(autd3::Point3::origin())}).open(autd3::link::Nop::builder());
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.Builder([new AUTD3(Point3.Origin)]).Open(Nop.Builder());
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.audit import Audit
autd = Controller.builder([AUTD3([0.0, 0.0, 0.0])]).open(Audit.builder())
tr = autd[0][0]
idx = tr.idx
dev_idx = tr.dev_idx
position = tr.position
Link
Link is an interface to AUTD3 devices. You need to choose one of the following.
TwinCAT
TwinCAT is a software that enables EtherCAT communication on Windows. TwinCAT is the only official way to use EtherCAT on Windows. TwinCAT is a very special software that supports only Windows and forcibly real-timeizes Windows.
You have to use a specific network controller to use TwinCAT. Please see List of supported network controllers.
Note: Alternatively, after installing TwinCAT, you can check the Vendor ID and Device ID of the corresponding device in
C:/TwinCAT/3.1/Driver/System/TcI8254x.inf
, and check it against "Device Manager" → "Ethernet Adapter" → "Property" → "Details" → "Hardware ID" to confirm.
The NIC other than the above may work, but in that case, normal operation and real-time performance are not guaranteed.
TwinCAT installation
TwinCAT can not coexist with Hyper-V or Virtual Machine Platform. Therefore, it is necessary to disable these functions. For example, start PowerShell with administrator privileges and type the following:
Disable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V-Hypervisor
Disable-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform
First, download TwinCAT XAE from the official site. To download, you need to register (free).
After downloading, run the installer and follow the instructions. At this time, check "TwinCAT XAE Shell install" and uncheck "Visual Studio Integration".
After installation, restart the PC and run C:/TwinCAT/3.1/System/win8settick.bat
as an administrator, and restart again.
AUTD Server
To use TwinCAT Link, install AUTD Server
first.
The AUTD server's installer is distributed on GitHub Releases.
When you run AUTD Server
, the following screen will appear, so open the "TwinCAT" tab.
Config file and driver installation
At the first time, you need to install configuration file and driver for EherCAT.
First click "Copy AUTD.xml" button. If you see a message like "AUTD.xml is successfully copied", it is successful.
Then, click "Open XAE shell" button". Open the upper menu of TwinCAT XAE Shell, select "TwinCAT" → "Show Realtime Ethernet Compatible Devices", select the corresponding device in "Compatible devices", and click "Install". If you see the installed device in "Installed and ready to use devices (realtime capable)", it is successful.
Note: If "Compatible devices" is not displayed, the Ethernet device on that PC is not compatible with TwinCAT. In that case, you can install the driver in "Incompatible devices", but it is not guaranteed to work.
License
In addition, the first time you run it, you will get a license error. Open "Solution Explorer" → "SYSTEM" → "License" in XAE Shell, click "7 Days Trial License ...", and enter the characters displayed on the screen. Note that the license is a 7-day trial license, but you can reissue it by doing the same thing again. After issuing the license, close TwinCAT XAE Shell and run again.
Run AUTD Server
Connect AUTD3 devices and PC, and power on the devices. Then, click "Run" button on AUTD Server. Here, let "Client IP address" be empty.
If you got a message like "AUTDs are found and added", it is successful.
TwinCAT link API
Constructor
use autd3::prelude::*;
use autd3_link_twincat::TwinCAT;
#[allow(unused_variables)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let autd = Controller::builder([AUTD3::new(Point3::origin())])
.open(
TwinCAT::builder()
)?;
Ok(())
}
#include "autd3/link/twincat.hpp"
#include<autd3.hpp>
int main() {
auto autd = autd3::ControllerBuilder({autd3::AUTD3(autd3::Point3::origin())})
.open(
autd3::link::TwinCAT::builder()
);
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Link;
using AUTD3Sharp.Gain;
using AUTD3Sharp.Modulation;
using AUTD3Sharp.Utils;
var autd = Controller.Builder([new AUTD3(Point3.Origin)]).Open(
TwinCAT.Builder()
);
from pyautd3.link.twincat import TwinCAT
TwinCAT.builder()
Troubleshooting
When using a large number of devices, the following error may occur.
In this case, increase the values of the Sync0 cycle time
and Send task cycle time
options of AUTD Server
and run again.
The values should be as small as possible while no error occurs.
RemoteTwinCAT
As mentioned above, AUTD3 and TwinCAT can only be used on Windows OS.
If you want to develop on a PC other than Windows, you can use RemoteTwinCAT
link to remotely operate TwinCAT from Linux/macOS.
Setup
You need to prepare two PCs to use RemoteTwinCAT. In this case, one of the PCs must be able to use the above TwinCAT link. This PC is called "server" here. On the other hand, there are no particular restrictions on the PC on the development side, that is, the side using the SDK, and it just needs to be connected to the same LAN as the server.
First, connect the server and the AUTD device. The LAN adapter used at this time must be a TwinCAT compatible adapter, just like the TwinCAT link. Also, connect the server and the client on different LANs. The LAN adapter here does not need to be TwinCAT compatible. Then, check the IP of the LAN between the server and the client. Here, for example, the server side is "172.16.99.104", and the client side is "172.16.99.62".
Next, start AUTD Server
on the server.
At this time, specify the IP address of the client (in this example, 172.16.99.62
) with the Client IP address
option.
You can see "Server AmsNetId" and "Client AmsNetId" on the right panel.
RemoteTwinCAT link API
Constructor
You must specify the "Server AmsNetId" in the constructor of RemoteTwinCAT
link.
And, specify the "Client AmsNetId" in the with_client_ams_net_id
method.
Also, specify the IP address of the server in the with_server_ip
method.
These two values is optional, but it is recommended to specify them.
use autd3::prelude::*;
use autd3_link_twincat::RemoteTwinCAT;
#[allow(unused_variables)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let autd = Controller::builder([AUTD3::new(Point3::origin())])
.open(
RemoteTwinCAT::builder("172.16.99.111.1.1")
.with_server_ip("172.16.99.104")
.with_client_ams_net_id("172.16.99.62.1.1")
)?;
Ok(())
}
#include<autd3.hpp>
#include "autd3/link/twincat.hpp"
int main() {
auto autd = autd3::ControllerBuilder({autd3::AUTD3(autd3::Point3::origin())})
.open(
autd3::link::RemoteTwinCAT::builder("172.16.99.111.1.1")
.with_server_ip("172.16.99.104")
.with_client_ams_net_id("172.16.99.62.1.1")
);
return 0; }
using System.Net;
using AUTD3Sharp;
using AUTD3Sharp.Link;
using AUTD3Sharp.Gain;
using AUTD3Sharp.Modulation;
using AUTD3Sharp.Utils;
var autd = Controller.Builder([new AUTD3(Point3.Origin)]).Open(
RemoteTwinCAT.Builder("172.16.99.111.1.1")
.WithServerIp("172.16.99.104")
.WithClientAmsNetId("172.16.99.62.1.1")
);
from pyautd3.link.twincat import RemoteTwinCAT
RemoteTwinCAT.builder("172.16.99.111.1.1")\
.with_server_ip("172.16.99.104")\
.with_client_ams_net_id("172.16.99.62.1.1")
Firewall
If you get a TCP-related error when using RemoteTwinCAT, the ADS protocol may be blocked by the firewall. In that case, allow the connection of TCP/UDP port 48898 in the firewall settings.
SOEM
SOEM is an open source EtherCAT master library developed by volunteers. Unlike TwinCAT, it runs on a regular Windows PC, so real-time performance is not guaranteed. Therefore, it is recommended to use TwinCAT. SOEM should be used only if there is no other choice or only during development. On the other hand, SOEM is cross-platform and easy to install.
If you are using Windows, install npcap in WinPcap API compatible mode. If you are using Linux/macOS, no special preparation is required.
NOTE: If you are using
SOEM
, be aware that it takes about 10-20 seconds after openingController
for the EtherCAT slaves to synchronize with each other. This period is subject to individual differences and changes depending on the synchronization signal/transmission cycle. During this period, the ultrasound synchronization between devices is not guaranteed.
SOEM link API
Following options can be specified for SOEM link.
use std::num::NonZeroUsize;
use std::time::Duration;
use autd3_link_soem::{SOEM, TimerStrategy, Status, ThreadPriority};
#[cfg(target_os = "windows")]
use autd3_link_soem::ProcessPriority;
#[allow(unused_variables)]
fn main() {
let builder =
SOEM::builder()
.with_ifname("")
.with_buf_size(NonZeroUsize::new(32).unwrap())
.with_err_handler(|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);
}
})
.with_state_check_interval(Duration::from_millis(100))
.with_sync0_cycle(Duration::from_millis(1))
.with_send_cycle(Duration::from_millis(1))
.with_timer_strategy(TimerStrategy::SpinSleep)
.with_sync_tolerance(Duration::from_micros(1))
.with_sync_timeout(Duration::from_secs(10))
.with_thread_priority(ThreadPriority::Max)
;
#[cfg(target_os = "windows")]
{
let builder =
builder
// Only available on Windows
.with_process_priority(ProcessPriority::High)
;
}
}
#include<autd3.hpp>
#include <iostream>
#include <autd3_link_soem.hpp>
int main() {
(void)
autd3::link::SOEM::builder()
.with_ifname("")
.with_buf_size(32)
.with_err_handler([](const uint16_t slave,
const autd3::link::Status status) {
std::cout << "slave [" << slave << "]: " << status << std::endl;
if (status == autd3::link::Status::Lost()) {
// You can also wait for the link to recover, without exiting the
// process
exit(-1);
}
})
.with_sync0_cycle(std::chrono::milliseconds(1))
.with_send_cycle(std::chrono::milliseconds(1))
.with_timer_strategy(autd3::link::TimerStrategy::SpinSleep)
.with_state_check_interval(std::chrono::milliseconds(100))
.with_sync_tolerance(std::chrono::microseconds(1))
.with_sync_timeout(std::chrono::seconds(10))
.with_thread_priority(autd3::link::ThreadPriority::Max)
.with_process_priority(autd3::link::ProcessPriority::High)
;
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Link;
using AUTD3Sharp.Gain;
using AUTD3Sharp.Modulation;
using AUTD3Sharp.Utils;
var autd = Controller.Builder([new AUTD3(Point3.Origin)]).Open(
SOEM.Builder()
.WithIfname("")
.WithBufSize(32)
.WithErrHandler((slave, status) =>
{
Console.Error.WriteLine($"slave [{slave}]: {status}");
if (status == Status.Lost)
{
// You can also wait for the link to recover, without exiting the process
Environment.Exit(-1);
}
})
.WithStateCheckInterval(Duration.FromMillis(100))
.WithSync0Cycle(Duration.FromMillis(1))
.WithSendCycle(Duration.FromMillis(1))
.WithTimerStrategy(TimerStrategy.SpinSleep)
.WithSyncTolerance(Duration.FromMicros(1))
.WithSyncTimeout(Duration.FromSecs(10))
.WithThreadPriority(AUTD3Sharp.Link.ThreadPriority.Max)
.WithProcessPriority(ProcessPriority.High)
);
import os
from pyautd3 import Duration
from pyautd3_link_soem import (
SOEM,
ProcessPriority,
Status,
ThreadPriority,
TimerStrategy,
)
def err_handler(slave: int, status: Status) -> None:
print(f"slave [{slave}]: {status}")
if status == Status.Lost():
# You can also wait for the link to recover, without exiting the process
os._exit(-1)
SOEM.builder()\
.with_ifname("")\
.with_buf_size(32)\
.with_err_handler(err_handler)\
.with_state_check_interval(Duration.from_millis(100))\
.with_sync0_cycle(Duration.from_millis(1))\
.with_send_cycle(Duration.from_millis(1))\
.with_timer_strategy(TimerStrategy.SpinSleep)\
.with_sync_tolerance(Duration.from_micros(1))\
.with_sync_timeout(Duration.from_secs(10))\
.with_thread_priority(ThreadPriority.Max)\
.with_process_priority(ProcessPriority.High)
ifname
: Network interface name. The default is blank, and if it is blank, the network interface to which the AUTD3 device is connected is automatically selected.buf_size
: Send queue buffer size. Usually, you don't need to change it.err_handler
: Callback when an error occurs. The callback function takes the device number where the error occurred, the type of error, and the error message as arguments.state_check_interval
: Interval to check if there is an error. The default is .sync0_cycle
: Synchronization signal cycle. The default is .send_cycle
: Send cycle. The default is 2 is .SOEM
may become unstable when a large number of devices are connected1. In this case, increase the values ofsync0_cycle
andsend_cycle
. These values should be as small as possible without causing errors. The value depends on the number of devices connected. For example, if there are 9 devices, set the value to or .
timer_strategy
: Timer strategy. The default isSpinSleep
.StdSleep
: Use standard library sleep.SpinSleep
: Use spin_sleep crate.SpinWait
: Use spin loop. High resolution but high CPU load.
sync_mode
: Synchronization mode. See Beckhoff's explanation for details.
RemoteSOEM
This link is used to separate the server PC running SOEM
and the client PC running the user program.
To use RemoteSOEM
, you need to prepare two PCs.
In this case, one PC must be able to use the SOEM
link.
This PC is called the "server" here.
On the other hand, there are no particular restrictions on the PC on the development side that uses the SDK, and it is sufficient to be connected to the same LAN as the server.
This is called the "client" here.
First, connect the server and the AUTD device. Then, connect the server and the client on different LANs2. Then, check the IP of the LAN between the server and the client. For example, suppose the server is "172.16.99.104", and the client is "172.16.99.62".
AUTD Server
To use RemoteSOEM
, install AUTD Server
first.
The AUTD server's installer is distributed on GitHub Releases.
When you run AUTD Server
, the following screen will appear, so open the "SOEM" tab.
Set port number and click "Run" button.
RemoteSOEM link API
RemoteSOEM
constructor takes
use autd3::prelude::*;
use autd3_link_soem::RemoteSOEM;
#[allow(unused_variables)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let autd = Controller::builder([AUTD3::new(Point3::origin())])
.open(
RemoteSOEM::builder("172.16.99.104:8080".parse()?)
)?;
Ok(())
}
#include<autd3.hpp>
#include <autd3_link_soem.hpp>
int main() {
auto autd = autd3::ControllerBuilder({autd3::AUTD3(autd3::Point3::origin())})
.open(
autd3::link::RemoteSOEM::builder("172.16.99.104:8080")
);
return 0; }
using System.Net;
using AUTD3Sharp;
using AUTD3Sharp.Link;
using AUTD3Sharp.Gain;
using AUTD3Sharp.Modulation;
using AUTD3Sharp.Utils;
var autd = Controller.Builder([new AUTD3(Point3.Origin)]).Open(
RemoteSOEM.Builder(new IPEndPoint(IPAddress.Parse("172.16.99.104"), 8080))
);
from pyautd3_link_soem import RemoteSOEM
RemoteSOEM.builder("172.16.99.104:8080")
SOEMAUTDServer
You can set options for SOEM
with the option argument of SOEMAUTDServer
.
Please see SOEMAUTDServer --help
for details.
Firewall
If you get a TCP-related error when using RemoteSOEM
, it may be blocked by the firewall.
It is looser than TwinCAT, and sometimes it works normally.
It can be used even with wireless LAN.
Simulator
Simulator link is a link used when using AUTD simulator.
Before using this link, you need to start AUTD simulator.
Simulator link API
Contructor
Simulator link's constructor takes an IP address and a port number of AUTD simulator.
use autd3::prelude::*;
use autd3_link_simulator::Simulator;
#[allow(unused_variables)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let autd = Controller::builder([AUTD3::new(Point3::origin())])
.open(
Simulator::builder("127.0.0.1:8080".parse()?)
)?;
Ok(())
}
#include<autd3.hpp>
#include "autd3/link/simulator.hpp"
int main() {
auto autd = autd3::ControllerBuilder({autd3::AUTD3(autd3::Point3::origin())})
.open(
autd3::link::Simulator::builder("127.0.0.1:8080")
);
return 0; }
using AUTD3Sharp;
using System.Net;
using AUTD3Sharp.Link;
using AUTD3Sharp.Gain;
using AUTD3Sharp.Modulation;
using AUTD3Sharp.Utils;
var autd = Controller.Builder([new AUTD3(Point3.Origin)]).Open(
Simulator.Builder(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080))
);
from pyautd3.link.simulator import Simulator
Simulator.builder("127.0.0.1:8080")
Gain
AUTD can control the phase/amplitude of each transducer individually, which enables us to generate various sound fields.
Gain
is a class to manage this, and the SDK provides some Gain
s to generate various sound fields.
Null
Null
is a Gain
with zero amplitude.
use autd3::prelude::*;
#[allow(unused_variables)]
fn main() {
let g = Null::new();
}
#include<autd3.hpp>
int main() {
autd3::Null g;
return 0; }
using AUTD3Sharp.Gain;
var g = new Null();
from pyautd3 import Null
g = Null()
Focus
Focus
generates a single focal point.
use autd3::prelude::*;
#[allow(unused_variables)]
fn main() {
let x = 0.;
let y = 0.;
let z = 0.;
let g = Focus::new(Point3::new(x, y, z));
}
#include<autd3.hpp>
int main() {
const auto x = 0.0;
const auto y = 0.0;
const auto z = 0.0;
const auto g = autd3::Focus(autd3::Point3(x, y, z));
return 0; }
using AUTD3Sharp.Utils;
using AUTD3Sharp.Gain;
var x = 0.0f;
var y = 0.0f;
var z = 0.0f;
var g = new Focus(new Point3(x, y, z));
from pyautd3 import Focus
x = 1.0
y = 0.0
z = 0.0
g = Focus([x, y, z])
Set intensity
You can change emission intensity by with_intensity
method.
use autd3::prelude::*;
#[allow(unused_variables)]
fn main() {
let x = 0.;
let y = 0.;
let z = 0.;
let g = Focus::new(Point3::new(x, y, z))
.with_intensity(0xFF)
.with_phase_offset(0x00);
}
#include<autd3.hpp>
int main() {
const auto x = 0.0;
const auto y = 0.0;
const auto z = 0.0;
const auto g = autd3::Focus(autd3::Point3(x, y, z))
.with_intensity(0xFF)
.with_phase_offset(0x00);
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Utils;
using AUTD3Sharp.Gain;
var x = 0.0f;
var y = 0.0f;
var z = 0.0f;
var g = new Focus(new Point3(x, y, z))
.WithIntensity(0xFF)
.WithPhaseOffset(0x00);
from pyautd3 import Focus
x = 0.0
y = 0.0
z = 0.0
g = Focus([x, y, z])\
.with_intensity(0xFF)\
.with_phase_offset(0x00)
Bessel
BesselBeam
generates a Bessel beam.
This Gain
is based on the paper by Hasegawa et al 1.
use autd3::prelude::*;
#[allow(unused_variables)]
fn main() {
let x = 0.;
let y = 0.;
let z = 0.;
let nx = 0.;
let ny = 0.;
let nz = 0.;
let theta = 0.;
let g = Bessel::new(
Point3::new(x, y, z),
UnitVector3::new_normalize(Vector3::new(nx, ny, nz)),
theta * rad,
);
}
#include<autd3.hpp>
int main() {
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;
const auto g = autd3::Bessel(autd3::Point3(x, y, z), autd3::Vector3(nx, ny, nz),
theta* autd3::rad);
return 0; }
from pyautd3 import Bessel, rad
x = 0.0
y = 0.0
z = 0.0
nx = 1.0
ny = 0.0
nz = 0.0
theta = 0.0
g = Bessel([x, y, z], [nx, ny, nz], theta * rad)
from pyautd3 import Bessel, rad
x = 0.0
y = 0.0
z = 0.0
nx = 1.0
ny = 0.0
nz = 0.0
theta = 0.0
g = Bessel([x, y, z], [nx, ny, nz], theta * rad)
The first argument of the constructor is the apex of the virtual cone producing the beam, the second argument is the direction of the beam, and the third argument is the angle between the plane perpendicular to the beam and the side of the virtual cone producing the beam ( in the figure below).
Set intensity
You can change emission intensity by with_intensity
method.
use autd3::prelude::*;
#[allow(unused_variables)]
fn main() {
let x = 0.;
let y = 0.;
let z = 0.;
let nx = 0.;
let ny = 0.;
let nz = 0.;
let theta = 0.;
let g = Bessel::new(
Point3::new(x, y, z),
UnitVector3::new_normalize(Vector3::new(nx, ny, nz)),
theta * rad
)
.with_intensity(0xFF)
.with_phase_offset(0x00);
}
#include<autd3.hpp>
#include <limits>
int main() {
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;
const auto g = autd3::Bessel(autd3::Point3(x, y, z), autd3::Vector3(nx, ny, nz),
theta* autd3::rad)
.with_intensity(0xFF)
.with_phase_offset(0x00);
return 0; }
from pyautd3 import Bessel, rad
x = 0.0
y = 0.0
z = 0.0
nx = 1.0
ny = 0.0
nz = 0.0
theta = 0.0
g = Bessel([x, y, z], [nx, ny, nz], theta * rad)\
.with_intensity(0xFF)\
.with_phase_offset(0x00)
from pyautd3 import Bessel, rad
x = 0.0
y = 0.0
z = 0.0
nx = 1.0
ny = 0.0
nz = 0.0
theta = 0.0
g = Bessel([x, y, z], [nx, ny, nz], theta * rad)\
.with_intensity(0xFF)\
.with_phase_offset(0x00)
Hasegawa, Keisuke, et al. "Electronically steerable ultrasound-driven long narrow air stream." Applied Physics Letters 111.6 (2017): 064104.
Plane
Plane
output a plane wave.
use autd3::prelude::*;
#[allow(unused_variables)]
fn main() {
let x = 0.;
let y = 0.;
let z = 0.;
let nx = 0.;
let ny = 0.;
let nz = 0.;
let theta = 0.;
let g = Plane::new(UnitVector3::new_normalize(Vector3::new(nx, ny, nz)));
}
#include<autd3.hpp>
int main() {
const auto nx = 0.0;
const auto ny = 0.0;
const auto nz = 1.0;
auto g = autd3::Plane(autd3::Vector3(nx, ny, nz));
return 0; }
using AUTD3Sharp.Utils;
using AUTD3Sharp.Gain;
var nx = 0.0f;
var ny = 0.0f;
var nz = 1.0f;
var g = new Plane(new Vector3(nx, ny, nz));
from pyautd3 import Plane
nx = 1.0
ny = 0.0
nz = 0.0
g = Plane([nx, ny, nz])
Specify the direction of the plane wave in the constructor of Plane
.
Set intensity
You can change emission intensity by with_intensity
method.
use autd3::prelude::*;
#[allow(unused_variables)]
fn main() {
let x = 0.;
let y = 0.;
let z = 0.;
let nx = 0.;
let ny = 0.;
let nz = 0.;
let theta = 0.;
let g = Plane::new(UnitVector3::new_normalize(Vector3::new(nx, ny, nz)))
.with_intensity(0xFF)
.with_phase_offset(0x00);
}
#include<autd3.hpp>
#include <limits>
int main() {
const auto nx = 0.0;
const auto ny = 0.0;
const auto nz = 1.0;
auto g = autd3::Plane(autd3::Vector3(nx, ny, nz))
.with_intensity(0xFF)
.with_phase_offset(0x00);
return 0; }
using AUTD3Sharp.Utils;
using AUTD3Sharp.Gain;
using AUTD3Sharp;
var nx = 0.0f;
var ny = 0.0f;
var nz = 1.0f;
var g = new Plane(new Vector3(nx, ny, nz))
.WithIntensity(0xFF)
.WithPhaseOffset(0x00);
from pyautd3 import Plane
nx = 1.0
ny = 0.0
nz = 0.0
g = Plane([nx, ny, nz])\
.with_intensity(0xFF)\
.with_phase_offset(0x00)
Uniform
Uniform
set the same amplitude and phase to all transducers.
use autd3::prelude::*;
#[allow(unused_variables)]
fn main() {
let g = Uniform::new((EmitIntensity::new(0xFF), Phase::new(0x00)));
}
#include<autd3.hpp>
#include <limits>
int main() {
const auto g = autd3::Uniform(autd3::EmitIntensity(0xFF), autd3::Phase(0x00));
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Gain;
var g = new Uniform((new EmitIntensity(0xFF), new Phase(0x00)));
from pyautd3 import Uniform, EmitIntensity, Phase
g = Uniform((EmitIntensity(0xFF), Phase(0x00)))
The intensity and phase are optional, and the default values are 0xFF and 0x00, respectively.
Group
Group
is a Gain
to use different Gain
for each transducer.
use autd3::prelude::*;
#[allow(unused_variables)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let x = 0.;
let y = 0.;
let z = 0.;
let g = Group::new(|_dev| |tr| match tr.idx() {
0..=100 => Some("null"),
_ => Some("focus"),
})
.set("null", Null::new())?
.set("focus", Focus::new(Point3::new(x, y, z)))?;
Ok(())
}
#include<optional>
#include<autd3.hpp>
int main() {
const auto x = 0.0;
const auto y = 0.0;
const auto z = 0.0;
const auto g = autd3::Group([](const auto& dev) {
return [](const auto& tr) -> std::optional<const char*> {
if (tr.idx() <= 100) return "null";
return "focus";
};
})
.set("null", autd3::gain::Null())
.set("focus", autd3::gain::Focus(autd3::Point3(x, y, z)));
return 0; }
using AUTD3Sharp.Utils;
using AUTD3Sharp.Gain;
var x = 0.0f;
var y = 0.0f;
var z = 0.0f;
var g = new Group(dev => tr => tr.Idx <= 100 ? "null" : "focus")
.Set("null", new Null())
.Set("focus", new Focus(new Point3(x, y, z)));
from pyautd3 import Focus, Group, Null
x = 1.0
y = 0.0
z = 0.0
g = (
Group(lambda _: lambda tr: "null" if tr.idx <= 100 else "focus")
.set("null", Null())
.set("focus", Focus([x, y, z]))
)
In the above case, transducers whose local indices are less or equal than 100 produce Null
, and the others produce Focus
.
NOTE: In this sample, we use string as a key, but you can use any type that can be used as a key of HashMap.
Holo
Holo is a Gain
for generating multiple foci.
Several algorithms for generating multiple foci have been proposed, and the following algorithms are implemented in SDK.
Naive
- Linear synthesis of single-focus solutionsGS
- Gershberg-Saxon, based on Marzo et al.1GSPAT
- Gershberg-Saxon for Phased Arrays of Transducers, based on Plasencia et al.2LM
- Levenberg-Marquardt, LM method proposed by Levenberg 3 and Marquardt 4 for optimization of nonlinear least-squares problems, implementation based on Madsen's text5Greedy
- Greedy algorithm and Brute-force search, based on Suzuki et al.6
You can select the backend for the calculation of the algorithm from the following.
NalgebraBackend
- uses NalgebraCUDABackend
- uses CUDA, which runs on GPUs (only available in Rust)
use autd3::prelude::*;
use autd3_gain_holo::{NalgebraBackend, GSPAT, Pa};
#[allow(unused_variables)]
fn main() {
let x1 = 0.;
let y1 = 0.;
let z1 = 0.;
let x2 = 0.;
let y2 = 0.;
let z2 = 0.;
let backend = std::sync::Arc::new(NalgebraBackend::default());
let g = GSPAT::new(
backend,
[
(Point3::new(x1, y1, z1), 5e3 * Pa),
(Point3::new(x2, y2, z2), 5e3 * Pa),
],
);
}
#include<autd3.hpp>
#include "autd3/gain/holo.hpp"
using autd3::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;
std::vector<std::pair<autd3::Point3, autd3::gain::holo::Amplitude>> foci = {
{autd3::Point3(x1, y1, z1), 5e3 * autd3::gain::holo::Pa},
{autd3::Point3(x2, y2, z2), 5e3 * autd3::gain::holo::Pa},
};
const auto backend = std::make_shared<autd3::gain::holo::NalgebraBackend>();
auto g = autd3::gain::holo::GSPAT(backend, foci);
return 0; }
using AUTD3Sharp.Gain.Holo;
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();
var g = new GSPAT(backend, [
(new Point3(x1, y1, z1), 5e3f * Pa),
(new Point3(x2, y2, z2), 5e3f * Pa)
]);
import numpy as np
from pyautd3.gain.holo import GSPAT, NalgebraBackend, Pa
x1 = 0.0
y1 = 0.0
z1 = 0.0
x2 = 0.0
y2 = 0.0
z2 = 0.0
backend = NalgebraBackend()
g = GSPAT(
backend,
[(np.array([x1, y1, z1]), 5e3 * Pa), (np.array([x2, y2, z2]), 5e3 * Pa)],
)
The constructor argument of each algorithm is backend
.
The add_focus
function specifies the position of each focus and the amplitude.
Amplitude constraint
Each algorithm's calculation result must be limited to the range that the transducer can output.
This can be controlled by with_constraint
, and one of the following four must be specified.
- DontCare: Do nothing.
- Normalize: Divide the amplitude of all transducers by the maximum amplitude and normalize it.
- Uniform: Set the amplitude of all transducers to the specified value.
- Clamp: Clamp the amplitude to the specified range.
use autd3::prelude::*;
use autd3_gain_holo::{EmissionConstraint, NalgebraBackend, Pa, GSPAT};
#[allow(unused_variables)]
fn main() {
let x1 = 0.;
let y1 = 0.;
let z1 = 0.;
let x2 = 0.;
let y2 = 0.;
let z2 = 0.;
let foci = [
(Point3::new(x1, y1, z1), 5e3 * Pa),
(Point3::new(x2, y2, z2), 5e3 * Pa),
];
let backend = std::sync::Arc::new(NalgebraBackend::default());
let g =
GSPAT::new(backend, foci).with_constraint(EmissionConstraint::Uniform(EmitIntensity::MAX));
}
#include<autd3.hpp>
#include <limits>
#include "autd3/gain/holo.hpp"
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;
std::vector<std::pair<autd3::Point3, autd3::gain::holo::Amplitude>> foci = {
{autd3::Point3(x1, y1, z1), 5e3 * autd3::gain::holo::Pa},
{autd3::Point3(x2, y2, z2), 5e3 * autd3::gain::holo::Pa},
};
const auto backend = std::make_shared<autd3::gain::holo::NalgebraBackend>();
auto g = autd3::gain::holo::GSPAT(backend, foci)
.with_constraint(autd3::gain::holo::EmissionConstraint::Uniform(
std::numeric_limits<autd3::EmitIntensity>::max()));
return 0; }
using AUTD3Sharp.Gain.Holo;
using AUTD3Sharp;
using AUTD3Sharp.Utils;
using static AUTD3Sharp.Units;
var backend = new NalgebraBackend();
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 foci = new[] { (new Point3(x1, y1, z1), 5e3f * Pa), (new Point3(x2, y2, z2), 5e3f * Pa) };
var g = new GSPAT(backend, foci)
.WithConstraint(EmissionConstraint.Uniform(EmitIntensity.Max));
import numpy as np
from pyautd3 import EmitIntensity
from pyautd3.gain.holo import GSPAT, EmissionConstraint, NalgebraBackend, Pa
backend = NalgebraBackend()
x1 = 0.0
y1 = 0.0
z1 = 0.0
x2 = 0.0
y2 = 0.0
z2 = 0.0
foci = [(np.array([x1, y1, z1]), 5e3 * Pa), (np.array([x2, y2, z2]), 5e3 * Pa)]
g = GSPAT(backend, foci).with_constraint(
EmissionConstraint.Uniform(EmitIntensity.maximum()),
)
Optimization parameters
Each algorithm has additional parameters.
These are all specified by with_xxx
.
use std::num::NonZeroUsize;
use autd3::prelude::*;
use autd3_gain_holo::{NalgebraBackend, GSPAT, Pa};
#[allow(unused_variables)]
fn main() {
let x1 = 0.;
let y1 = 0.;
let z1 = 0.;
let x2 = 0.;
let y2 = 0.;
let z2 = 0.;
let backend = std::sync::Arc::new(NalgebraBackend::default());
let g = GSPAT::new(
backend,
[
(Point3::new(x1, y1, z1), 5e3 * Pa),
(Point3::new(x2, y2, z2), 5e3 * Pa),
],
)
.with_repeat(NonZeroUsize::new(100).unwrap());
}
#include<autd3.hpp>
#include "autd3/gain/holo.hpp"
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;
std::vector<std::pair<autd3::Point3, autd3::gain::holo::Amplitude>> foci = {
{autd3::Point3(x1, y1, z1), 5e3 * autd3::gain::holo::Pa},
{autd3::Point3(x2, y2, z2), 5e3 * autd3::gain::holo::Pa},
};
const auto backend = std::make_shared<autd3::gain::holo::NalgebraBackend>();
auto g = autd3::gain::holo::GSPAT(backend, foci).with_repeat(100);
return 0; }
using AUTD3Sharp.Gain.Holo;
using AUTD3Sharp.Utils;
using static AUTD3Sharp.Units;
var backend = new NalgebraBackend();
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 foci = new[] { (new Point3(x1, y1, z1), 5e3f * Pa), (new Point3(x1, y1, z1), 5e3f * Pa) };
var g = new GSPAT(backend, foci).WithRepeat(100);
from pyautd3.gain.holo import GSPAT, NalgebraBackend
from pyautd3.gain.holo import Pa
import numpy as np
backend = NalgebraBackend()
x1 = 0.0
y1 = 0.0
z1 = 0.0
x2 = 0.0
y2 = 0.0
z2 = 0.0
foci = [(np.array([x1, y1, z1]), 5e3 * Pa), (np.array([x2, y2, z2]), 5e3 * Pa)]
g = GSPAT(backend, foci).with_repeat(100)
Please refar to each paper for more details.
Marzo, Asier, and Bruce W. Drinkwater. "Holographic acoustic tweezers." Proceedings of the National Academy of Sciences 116.1 (2019): 84-89.
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.
Levenberg, Kenneth. "A method for the solution of certain non-linear problems in least squares." Quarterly of applied mathematics 2.2 (1944): 164-168.
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.
Madsen, Kaj, Hans Bruun Nielsen, and Ole Tingleff. "Methods for non-linear least squares problems." (2004).
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
You can cache the calculation result of Gain
by with_cache
method.
use autd3::prelude::*;
#[allow(unused_variables)]
fn main() {
let g = Null::new().with_cache();
}
#include<autd3.hpp>
int main() {
const auto g = autd3::gain::Null().with_cache();
return 0; }
using AUTD3Sharp.Gain;
var g = new Null().WithCache();
from pyautd3.gain import Null
g = Null().with_cache()
Modulation
Modulation
is a mechanism to control AM modulation.
The modulation is applied to the amplitude of the sound pressure.
For example, if you use Sine
with , the sound pressure amplitude is as follows, and the envelope of the positive part (or negative part) of sound pressure follows the sine wave.
Currently, Modulation
has the following restrictions.
- The buffer size is up to 32768.
- The sampling rate is , where is a non-zero 16-bit unsigned integer.
The SDK has Modulation
by default to generate several types of AM.
Modulation common API
Sampling configuration
You can get the sampling frequency with sampling_config
.
use autd3::prelude::*;
#[allow(unused_variables)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let m = autd3::modulation::Sine::new(150 * Hz);
let fs = m.sampling_config().freq(); // -> 4kHz
Ok(())
}
#include<autd3.hpp>
#include<autd3/link/nop.hpp>
int main() {
const auto m = autd3::modulation::Sine(150 * autd3::Hz);
const auto fs = m.sampling_config().freq(); // -> 4kHz
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Modulation;
using static AUTD3Sharp.Units;
var m = new Sine(150u * Hz);
var fs = m.SamplingConfig.Freq; // -> 4kHz
from pyautd3 import Hz
from pyautd3.modulation import Sine
m = Sine(150 * Hz)
fs = m.sampling_config.freq # -> 4kHz
Some Modulation
can set the sampling configuration with with_sampling_config
.
use autd3::prelude::*;
#[allow(unused_variables)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let m = autd3::modulation::Sine::new(150 * Hz)
.with_sampling_config(4000 * Hz);
Ok(())
}
#include<autd3.hpp>
#include<autd3/link/nop.hpp>
int main() {
const auto m = autd3::modulation::Sine(150 * autd3::Hz)
.with_sampling_config(4000u * autd3::Hz);
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Modulation;
using static AUTD3Sharp.Units;
var m = new Sine(150u * Hz).WithSamplingConfig(4000 * Hz);
from pyautd3 import Hz
from pyautd3.modulation import Sine
m = Sine(150 * Hz).with_sampling_config(4000 * Hz)
Static
Without modulation.
use autd3::prelude::*;
#[allow(unused_variables)]
fn main() {
let m = Static::new();
}
#include<autd3.hpp>
int main() {
autd3::Static m;
return 0; }
using AUTD3Sharp.Modulation;
var m = new Static();
from pyautd3 import Static
m = Static()
Set intensity
You can set the emission intensity by with_intensity
.
use autd3::prelude::*;
#[allow(unused_variables)]
fn main() {
let m = Static::with_intensity(0xFF);
}
#include<autd3.hpp>
int main() {
const auto m = autd3::Static::with_intensity(0xFF);
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Modulation;
var m = Static.WithIntensity(0xFF);
from pyautd3 import Static
m = Static.with_intensity(0xFF)
Sine
Modulation
to transform the square of the sound pressure to a sine wave.
Specify the frequency as an integer in the constructor.
use autd3::prelude::*;
#[allow(unused_variables)]
fn main() {
let m = Sine::new(150 * Hz);
}
#include<autd3.hpp>
int main() {
autd3::Sine m(150 * autd3::Hz);
return 0; }
using AUTD3Sharp.Modulation;
using static AUTD3Sharp.Units;
var m = new Sine(150u * Hz);
from pyautd3 import Hz, Sine
m = Sine(150 * Hz)
Set intensity and offset
Sine
applies AM so that the waveform of the sound pressure is
Here, and can be specified by with_intensity
and with_offset
, respectively (defaults are 0xFF
and 0x80
).
use autd3::prelude::*;
#[allow(unused_variables)]
fn main() {
let m = Sine::new(150 * Hz)
.with_intensity(0xFF)
.with_offset(0xFF / 2);
}
#include<autd3.hpp>
int main() {
const auto m =
autd3::Sine(150 * autd3::Hz).with_intensity(0xFF).with_offset(0xFF / 2);
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Modulation;
using static AUTD3Sharp.Units;
var m = new Sine(150u * Hz)
.WithIntensity(0xFF)
.WithOffset(0xFF / 2);
from pyautd3 import Hz, Sine
m = Sine(150 * Hz).with_intensity(0xFF).with_offset(0xFF // 2)
Fourier
Fourier
is a modulation that generates a waveform by superimposing multiple Sine
.
use autd3::modulation::Fourier;
use autd3::prelude::*;
#[allow(unused_variables)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let m = Fourier::new([Sine::new(100 * Hz), Sine::new(150 * Hz)])?;
Ok(())
}
#include<autd3.hpp>
#include <autd3/modulation/fourier.hpp>
int main() {
const auto m =
autd3::modulation::Fourier({autd3::modulation::Sine(100 * autd3::Hz),
autd3::modulation::Sine(150 * autd3::Hz)});
return 0; }
using AUTD3Sharp.Modulation;
using static AUTD3Sharp.Units;
var m = new Fourier([new Sine(100u * Hz), new Sine(150u * Hz), new Sine(200u * Hz)]);
from pyautd3 import Hz, Sine
from pyautd3.modulation import Fourier
m = Fourier([Sine(100 * Hz), Sine(150 * Hz)])
Phase parameter of Sine
For Fourier
, Sine
has a feature to specify the phase parameter.
use autd3::modulation::Fourier;
use autd3::prelude::*;
#[allow(unused_variables)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let m = Fourier::new([
Sine::new(100 * Hz),
Sine::new(150 * Hz).with_phase(PI / 2.0 * rad),
]);
Ok(())
}
#include<autd3.hpp>
#include <autd3/modulation/fourier.hpp>
int main() {
const auto m = autd3::modulation::Fourier(
{autd3::modulation::Sine(100 * autd3::Hz),
autd3::modulation::Sine(150 * autd3::Hz)
.with_phase(autd3::pi / 2.0f * autd3::rad)});
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Modulation;
using static AUTD3Sharp.Units;
using System;
var m = new Fourier([new Sine(100u * Hz), new Sine(150 * Hz).WithPhase(MathF.PI / 2.0f * rad)]);
import numpy as np
from pyautd3 import Hz, rad, Sine
from pyautd3.modulation import Fourier
m = Fourier([Sine(100 * Hz), Sine(150 * Hz).with_phase(np.pi / 2.0 * rad)])
Square
Modulation
to transform sound pressure to a square wave.
Specify the frequency as an integer in the constructor.
use autd3::prelude::*;
#[allow(unused_variables)]
fn main() {
let m = Square::new(150 * Hz);
}
#include<autd3.hpp>
int main() {
autd3::Square m(150 * autd3::Hz);
return 0; }
using AUTD3Sharp.Modulation;
using static AUTD3Sharp.Units;
var m = new Square(150.0f * Hz);
from pyautd3 import Hz, Square
m = Square(150 * Hz)
Set amplitude
You can set the amplitude of the square wave with with_low
and with_high
for low level and high level, respectively.
use autd3::prelude::*;
#[allow(unused_variables)]
fn main() {
let m = Square::new(150 * Hz)
.with_low(0x00)
.with_high(0xFF);
}
#include<autd3.hpp>
int main() {
const auto m = autd3::Square(150 * autd3::Hz).with_low(0x00).with_high(0xFF);
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Modulation;
using static AUTD3Sharp.Units;
var m = new Square(150u * Hz)
.WithLow(0x00)
.WithHigh(0xFF);
from pyautd3 import Hz, Square
m = Square(150 * Hz).with_low(0x00).with_high(0xFF)
Set duty ratio
You can set the duty ratio of the square wave with with_duty
.
use autd3::prelude::*;
#[allow(unused_variables)]
fn main() {
let m = Square::new(150 * Hz).with_duty(0.5);
}
#include<autd3.hpp>
int main() {
const auto m = autd3::Square(150 * autd3::Hz).with_duty(0.5);
return 0; }
using AUTD3Sharp.Modulation;
using static AUTD3Sharp.Units;
var m = new Square(150u * Hz).WithDuty(0.5f);
from pyautd3 import Hz, Square
m = Square(150 * Hz).with_duty(0.5)
Wav
Wav
is a Modulation
constructed from a wav file.
use autd3_modulation_audio_file::Wav;
#[allow(unused_variables)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let path = "path/to/foo.wav";
let m = Wav::new(&path);
Ok(())
}
#include<autd3.hpp>
#include "autd3/modulation/audio_file.hpp"
int main() {
const auto path = "path/to/foo.wav";
const autd3::modulation::audio_file::Wav m(path);
return 0; }
using AUTD3Sharp.Modulation.AudioFile;
var path = "path/to/foo.wav";
var m = new Wav(path);
import pathlib
from pyautd3.modulation.audio_file import Wav
path = pathlib.Path("path/to/foo.wav")
m = Wav(path)
NOTE:
Wav
resamples raw pcm file data to the sampling frequency of Modulation. Please refer to Modulation for the setting and constraints of the sampling frequency ofModulation
.
RawPCM
RawPCM
is a Modulation
constructed from raw pcm file.
use autd3::prelude::*;
use autd3_modulation_audio_file::RawPCM;
#[allow(unused_variables)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let path = "path/to/foo.dat";
let m = RawPCM::new(&path, 4000 * Hz);
Ok(())
}
#include<autd3.hpp>
#include "autd3/modulation/audio_file.hpp"
int main() {
const auto path = "path/to/foo.fat";
const auto m = autd3::modulation::audio_file::RawPCM(path, 4000u * autd3::Hz);
return 0; }
using AUTD3Sharp.Modulation.AudioFile;
using static AUTD3Sharp.Units;
var path = "path/to/foo.dat";
var m = new RawPCM(path, 4000 * Hz);
import pathlib
from pyautd3 import Hz
from pyautd3.modulation.audio_file import RawPCM
path = pathlib.Path("path/to/foo.dat")
m = RawPCM(path, 4000 * Hz)
You need to specify the sampling frequency of this data as the second argument of the constructor.
NOTE:
RawPCM
resamples raw pcm file data to the sampling frequency of Modulation. Please refer to Modulation for the setting and constraints of the sampling frequency ofModulation
.
Cache
You can cache the calculation result of Modulation
by with_cache
method.
use autd3::prelude::*;
#[allow(unused_variables)]
fn main() {
let c = Static::new().with_cache();
}
#include<autd3.hpp>
int main() {
const auto m = autd3::modulation::Static().with_cache();
return 0; }
using AUTD3Sharp.Modulation;
var m = new Static().WithCache();
from pyautd3 import Static
m = Static().with_cache()
RadiationPressure
RadiationPressure
is a Modulation
to apply modulation to radiation pressure (proportional to the square of the sound pressure) instead of sound pressure.
For example, if you use RadiationPressure
on Sine
modulation with , the radiation pressure of the sound pressure amplitude is as follows, and the envelope of the radiation pressure follows the sine wave.
use autd3::prelude::*;
#[allow(unused_variables)]
fn main() {
let m = Sine::new(150 * Hz).with_radiation_pressure();
}
#include<autd3.hpp>
int main() {
const auto m =
autd3::modulation::Sine(150 * autd3::Hz).with_radiation_pressure();
return 0; }
using AUTD3Sharp.Modulation;
using static AUTD3Sharp.Units;
var m = new Sine(150u * Hz).WithRadiationPressure();
from pyautd3 import Hz, Sine
m = Sine(150 * Hz).with_radiation_pressure()
Spatio-Temporal Modulation
SDK provides a function to switch Gain
periodically (Spatio-Temporal Modulation, STM).
The SDK provides FociSTM
that supports 8 foci at maximum and GainSTM
that support arbitrary Gain
.
FociSTM
and GainSTM
use the timer on the AUTD3 hardware, so the time accuracy is high, but there are many restrictions.
FociSTM/GainSTM common API
frequency
Get the frequency of STM.
sampling_config
Get the sampling configuration of STM.
FociSTM
- The maximum number of sampling points is .
- The sampling frequency is .
THe following is an example of using FociSTM
to focus on a point directly above the center of the array with a radius of centered on the center of the array.
use autd3::prelude::*;
#[allow(unused_variables)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let autd = Controller::builder([AUTD3::new(Point3::origin())]).open(autd3::link::Nop::builder())?;
let center = autd.center() + Vector3::new(0., 0., 150.0 * mm);
let point_num = 200;
let radius = 30.0 * mm;
let stm = FociSTM::new(
1.0 * Hz,
(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
}),
)?;
Ok(())
}
#include <ranges>
#include<autd3.hpp>
#include<autd3/link/nop.hpp>
using namespace std::ranges::views;
int main() {
auto autd =
autd3::ControllerBuilder({autd3::AUTD3(autd3::Point3::origin())}).open(autd3::link::Nop::builder());
const autd3::Point3 center = autd.center() + autd3::Vector3(0, 0, 150);
const auto points_num = 200;
const auto radius = 30.0f;
autd3::FociSTM stm(1.0f * autd3::Hz,
iota(0) | take(points_num) | transform([&](auto i) {
const auto theta = 2.0f * autd3::pi *
static_cast<float>(i) /
static_cast<float>(points_num);
autd3::Point3 p =
center + radius * autd3::Vector3(std::cos(theta),
std::sin(theta), 0);
return p;
}));
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Utils;
using AUTD3Sharp.Link;
using static AUTD3Sharp.Units;
using var autd = Controller.Builder([new AUTD3(Point3.Origin)]).Open(Nop.Builder());
var center = autd.Center + new Vector3(0, 0, 150);
const int pointNum = 200;
const float radius = 30.0f;
var stm = new FociSTM(1.0f * Hz, 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);
}));
import numpy as np
from pyautd3 import AUTD3, Controller, FociSTM, Hz
from pyautd3.link.audit import Audit
autd = Controller.builder([AUTD3([0.0, 0.0, 0.0])]).open(Audit.builder())
center = autd.center + np.array([0.0, 0.0, 150.0])
point_num = 200
radius = 30.0
stm = FociSTM(
1.0 * Hz,
(
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))
),
)
FociSTM
's constructor takes the STM frequency as an argument.
Note that the specified frequency and the actual frequency may differ due to constraints on the number of sampling points and the sampling period.
For example, the above example runs 200 points at , so the sampling frequency should be .
However, if point_num=199
, the sampling frequency must be , but there is no integer that satisfies .
Therefore, the closest is selected.
As a result, the specified frequency and the actual frequency are shifted.
frequency
can be used to check the actual frequency.
Specify the sampling configuration
You can specify the sampling frequency by from_sampling_config
instead of frequency.
use autd3::prelude::*;
#[allow(unused_variables)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let stm = FociSTM::new(SamplingConfig::new(1 * Hz)?, [Point3::origin(), Point3::origin()]);
Ok(())
}
#include<autd3.hpp>
#include<autd3/link/nop.hpp>
int main() {
auto autd =
autd3::ControllerBuilder({autd3::AUTD3(autd3::Point3::origin())}).open(autd3::link::Nop::builder());
std::vector<std::array<autd3::Point3, 1>> foci = {
std::array{autd3::Point3(0, 0, 0)},
std::array{autd3::Point3(0, 0, 0)},
};
autd3::FociSTM stm(autd3::SamplingConfig(1 * autd3::Hz), foci);
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Utils;
using AUTD3Sharp.Link;
using static AUTD3Sharp.Units;
using var autd = Controller.Builder([new AUTD3(Point3.Origin)]).Open(Nop.Builder());
var stm = new FociSTM((SamplingConfig)(1u * Hz), [Point3.Origin, Point3.Origin]);
from pyautd3 import Hz, FociSTM, SamplingConfig
import numpy as np
foci = [np.array([0.0, 0.0, 0.0]), np.array([0.0, 0.0, 0.0])]
stm = FociSTM(SamplingConfig(1 * Hz), foci)
GainSTM
GainSTM
can handle arbitrary Gain
s, unlike FociSTM
.
However, the number of Gain
s that can be used is 1024.
The following is an example of how to use GainSTM
.
This is a sample that rotates the focus on a circle with a radius of centered on a point directly above the center of the array.
use autd3::prelude::*;
#[allow(unused_variables)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let autd = Controller::builder([AUTD3::new(Point3::origin())]).open(autd3::link::Nop::builder())?;
let center = autd.center() + Vector3::new(0., 0., 150.0 * mm);
let point_num = 200;
let radius = 30.0 * mm;
let stm = GainSTM::new(
1.0 * Hz,
(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::new(center + p)
}),
)?;
Ok(())
}
#include <ranges>
#include<autd3.hpp>
#include<autd3/link/nop.hpp>
using namespace std::ranges::views;
int main() {
auto autd =
autd3::ControllerBuilder({autd3::AUTD3(autd3::Point3::origin())}).open(autd3::link::Nop::builder());
const autd3::Point3 center = autd.center() + autd3::Vector3(0, 0, 150);
const auto points_num = 200;
const auto radius = 30.0f;
autd3::GainSTM stm(1.0f * autd3::Hz,
iota(0) | take(points_num) | transform([&](auto i) {
const auto theta = 2.0f * autd3::pi *
static_cast<float>(i) /
static_cast<float>(points_num);
return autd3::gain::Focus(
center + radius * autd3::Vector3(std::cos(theta),
std::sin(theta), 0));
}));
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Utils;
using AUTD3Sharp.Gain;
using AUTD3Sharp.Link;
using static AUTD3Sharp.Units;
using var autd = Controller.Builder([new AUTD3(Point3.Origin)]).Open(Nop.Builder());
var center = autd.Center + new Vector3(0, 0, 150);
const int pointNum = 200;
const float radius = 30.0f;
var stm = new GainSTM(1.0f * Hz, Enumerable.Range(0, pointNum).Select(i =>
{
var theta = 2.0f * MathF.PI * i / pointNum;
return new Focus(center + radius * new Vector3(MathF.Cos(theta), MathF.Sin(theta), 0));
}));
import numpy as np
from pyautd3 import AUTD3, Controller, Focus, GainSTM, Hz
from pyautd3.link.audit import Audit
autd = Controller.builder([AUTD3([0.0, 0.0, 0.0])]).open(Audit.builder())
center = autd.center + np.array([0.0, 0.0, 150.0])
point_num = 200
radius = 30.0
stm = GainSTM(
1.0 * Hz,
(
Focus(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))
),
)
Specify the sampling configuration
You can also specify the sampling frequency instead of frequency.
use autd3::prelude::*;
#[allow(unused_variables)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let stm = GainSTM::new(SamplingConfig::new(1 * Hz)?, [Null::new(), Null::new()]);
Ok(())
}
#include<autd3.hpp>
#include<autd3/link/nop.hpp>
int main() {
autd3::GainSTM stm(autd3::SamplingConfig(1 * autd3::Hz),
{autd3::gain::Null(), autd3::gain::Null()});
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Utils;
using AUTD3Sharp.Link;
using AUTD3Sharp.Gain;
using static AUTD3Sharp.Units;
using var autd = Controller.Builder([new AUTD3(Point3.Origin)]).Open(Nop.Builder());
var stm = new GainSTM((SamplingConfig)(1u * Hz), [new Null(), new Null()]);
from pyautd3 import Hz, GainSTM, Null, SamplingConfig
stm = GainSTM(SamplingConfig(1 * Hz), [Null(), Null()])
GainSTMMode
GainSTM
sends all phase/amplitude data, so it has a large latency1.
To solve this problem, GainSTM
has PhaseFull
mode that sends only phase and reduces the transmission time by half2.
This mode can be switched with with_mode
.
use autd3::prelude::*;
#[allow(unused_variables)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let stm = GainSTM::new(1.0 * Hz, [Null::new(), Null::new()])?
.with_mode(GainSTMMode::PhaseFull);
Ok(())
}
#include<autd3.hpp>
#include<autd3/link/nop.hpp>
int main() {
auto stm =
autd3::GainSTM(1.0f * autd3::Hz, {autd3::gain::Null(), autd3::gain::Null()})
.with_mode(autd3::GainSTMMode::PhaseFull);
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Utils;
using AUTD3Sharp.Link;
using AUTD3Sharp.Gain;
using static AUTD3Sharp.Units;
using var autd = Controller.Builder([new AUTD3(Point3.Origin)]).Open(Nop.Builder());
var stm = new GainSTM(1.0f * Hz, [new Null(), new Null()]).WithMode(GainSTMMode.PhaseFull);
from pyautd3 import Hz, GainSTM, GainSTMMode, Null
stm = GainSTM(1.0 * Hz, [Null(), Null()]).with_mode(GainSTMMode.PhaseFull)
The default is PhaseIntensityFull
mode, which sends all information.
About 75 times of FociSTM<1>
Legacy mode only
Silencer
AUTD3 has a silencer to mute the output. The silencer suppresses the rapid change in the drive signal of the transducer and mutes the output.
Theory
The silencer is based on the paper by Suzuki et al.1.
As a rough outline,
- Amplitude modulation of ultrasound produces audible sound.
- When driving an ultrasound transducer, phase changes cause amplitude fluctuations.
- Therefore, audible noise is generated.
- Amplitude fluctuations can be suppressed by linearly interpolating phase changes and changing them stepwise.
- Therefore, noise can be reduced by doing fine interpolation.
- The silencer is a method to reduce noise by doing fine interpolation.
Silencer Config
To configure the silencer, send Silencer
to the controller.
The silencer is enabled by default.
To disable the silencer, send Silencer::disable
.
use autd3::prelude::*;
#[allow(unused_variables)]
fn main() {
let config = Silencer::default();
let config = Silencer::disable();
}
#include<autd3.hpp>
int main() {
{
const auto config = autd3::Silencer();
}
{
const auto config = autd3::Silencer::disable();
}
return 0; }
using AUTD3Sharp;
{
var config = new Silencer();
}
{
var config = Silencer.Disable();
}
from pyautd3 import Silencer
config = Silencer()
config = Silencer.disable()
To configure the silencer more finely, you need to choose from the following two modes.
The default is fixed completion time mode.
Fixed update rate mode
Phase change by Silencer in Fixed update rate mode
Silencer changes the phase linearly and stepwise to mute the output. In other words, it is almost equivalent to passing the phase time series data through a (simple) moving average filter. However, it differs in that it takes into account the fact that the phase data is periodic.
For example, consider the case where the period of the ultrasound is . In other words, corresponds to and corresponds to . Here, suppose that the phase changes from to at time . The phase change by Silencer is as follows.
On the other hand, suppose that the phase changes from to at time . The phase change by Silencer is as follows. This is because is closer to than in terms of the phase.
That is, Silencer updates the phase as follows for the current and the target value .
Where is the update amount per step (step
of Silencer
).
And the update frequency is .
Small makes the phase change smoother and reduces noise.
According to this implementation, the behavior is different from the moving average filter. One is when the phase change amount shown above is larger than , and the other is when the phase changes again in the middle. Examples of phase changes at this time are shown below.
intensity change by Silencer in Fixed update rate mode
intensity modulation of ultrasound produces audible sound. So, AM noise can be reduced by applying a filter to the intensity parameter .
Unlike the phase, the intensity parameter is not periodic with respect to the period . Therefore, the intensity parameter is updated as follows for the current and the target value .
Configure fixed update rate mode
To configure the fixed update rate mode, do as follows. The arguments correspond to described above.
use std::num::NonZeroU16;
use autd3::prelude::*;
#[allow(unused_variables)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = Silencer::new(
FixedUpdateRate {
intensity: NonZeroU16::new(1).unwrap(),
phase: NonZeroU16::new(1).unwrap(),
}
);
Ok(())
}
#include<autd3.hpp>
int main() {
const auto config =
autd3::Silencer{autd3::FixedUpdateRate{.intensity = 1, .phase = 1}};
return 0; }
using AUTD3Sharp;
var config = new Silencer(new FixedUpdateRate
{
Intensity = 1,
Phase = 1
});
from pyautd3 import Silencer, FixedUpdateRate
config = Silencer(FixedUpdateRate(intensity=1, phase=1))
Fixed completion time mode
In fixed completion time mode, change of phase/intensity is completed in a fixed duration.
Configure fixed completion time mode
To configure the fixed completion time mode, do as follows.
use autd3::prelude::*;
#[allow(unused_variables)]
fn main() {
let config = Silencer::new(
FixedCompletionTime {
intensity: std::time::Duration::from_micros(250),
phase: std::time::Duration::from_micros(250),
}
);
}
#include<chrono>
#include<autd3.hpp>
int main() {
const auto config = autd3::Silencer{
autd3::FixedCompletionTime{.intensity = std::chrono::microseconds(250),
.phase = std::chrono::microseconds(250)}};
return 0; }
using AUTD3Sharp;
var config = new Silencer(new FixedCompletionTime
{
Intensity = Duration.FromMicros(250),
Phase = Duration.FromMicros(250)
});
from pyautd3 import Duration, FixedCompletionTime, Silencer
config = Silencer(
FixedCompletionTime(
intensity=Duration.from_micros(250),
phase=Duration.from_micros(250),
),
)
The default values are for phase change and for intensity change.
In this mode, an error is returned if the phase/intensity change of Modulation
, FociSTM
, or GainSTM
cannot be completed in the time specified by Silencer.
That is, the following conditions must be satisfied.
- Silencer's intensity change completion time sampling period of
Modulation
- Silencer's intensity change completion time sampling period of
FociSTM
/GainSTM
- Silencer's phase change completion time sampling period of
FociSTM
/GainSTM
If you set strict_mode
to false
, you can ignore these restrictions, but it's not recommended.
use autd3::prelude::*;
#[allow(unused_variables)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = Silencer::default().with_strict_mode(false);
Ok(())
}
#include<autd3.hpp>
int main() {
const auto config = autd3::Silencer().with_strict_mode(false);
return 0; }
using AUTD3Sharp;
var config = new Silencer().WithStrictMode(false);
from pyautd3 import Silencer
config = Silencer().with_strict_mode(False)
Suzuki, Shun, et al. "Reducing amplitude fluctuation by gradual phase shift in midair ultrasound haptics." IEEE transactions on haptics 13.1 (2020): 87-93.
Controller
The followings are introductino of APIs in Controller
class.
Force fan
AUTD3 device has a fan, and it has three fan modes: Auto, Off, and On.
In Auto mode, the temperature monitoring IC monitors the temperature of the IC, and when it exceeds a certain temperature, the fan starts automatically. In Off mode, the fan is always off, and in On mode, the fan is always on.
The fan mode is switched by the jumper switch next to the fan. As shown in the figure below, the fan side is shorted to switch to Auto, the center is Off, and the right is On.
You can force the fan to start in Auto mode by ForceFan
.
use autd3::prelude::*;
#[allow(unused_variables)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut autd = Controller::builder([AUTD3::new(Point3::origin())]).open(autd3::link::Nop::builder())?;
autd.send(ForceFan::new(|_dev| true))?;
Ok(())
}
#include<chrono>
#include<autd3.hpp>
#include<autd3/link/nop.hpp>
int main() {
auto autd =
autd3::ControllerBuilder({autd3::AUTD3(autd3::Point3::origin())}).open(autd3::link::Nop::builder());
autd.send(autd3::ForceFan([](const auto&) { return true; }));
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Link;
using AUTD3Sharp.Utils;
using var autd = Controller.Builder([new AUTD3(Point3.Origin)]).Open(Nop.Builder());
autd.Send(new ForceFan(_ => true));
from pyautd3 import ForceFan
from pyautd3 import Controller, AUTD3
from pyautd3.link.audit import Audit
autd = Controller.builder([AUTD3([0.0, 0.0, 0.0])]).open(Audit.builder())
autd.send(ForceFan(lambda _: True))
fpga_state
Get the FPGA status.
Before using this, you need to configure reads FPGA info flag by ReadsFPGAState
.
use autd3::prelude::*;
#[allow(unused_variables)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut autd = Controller::builder([AUTD3::new(Point3::origin())]).open(autd3::link::Nop::builder())?;
autd.send(ReadsFPGAState::new(|_dev| true))?;
let info = autd.fpga_state()?;
Ok(())
}
#include<autd3.hpp>
#include<autd3/link/nop.hpp>
int main() {
auto autd =
autd3::ControllerBuilder({autd3::AUTD3(autd3::Point3::origin())}).open(autd3::link::Nop::builder());
autd.send(autd3::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.Builder([new AUTD3(Point3.Origin)]).Open(Nop.Builder());
autd.Send(new ReadsFPGAState(_ => true));
var info = autd.FPGAState;
from pyautd3 import Controller, AUTD3, ReadsFPGAState
from pyautd3.link.audit import Audit
autd = Controller.builder([AUTD3([0.0, 0.0, 0.0])]).open(Audit.builder())
autd.send(ReadsFPGAState(lambda _: True))
info = autd.fpga_state
You can get the following information about the FPGA.
- thermal sensor for fan control is asserted or not
send
Send the data to the device.
You can send a single or two data at the same time.
Timeout
You can specify the timeout time with with_timeout
.
If you omit this, the timeout time set by Link will be used.
use std::time::Duration;
use autd3::prelude::*;
#[allow(unused_variables)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut autd = Controller::builder([AUTD3::new(Point3::origin())]).open(autd3::link::Nop::builder())?;
let m = Static::new();
let g = Null::new();
autd.send((m, g).with_timeout(Some(Duration::from_millis(20))))?;
Ok(())
}
#include<chrono>
#include<autd3.hpp>
#include<autd3/link/nop.hpp>
int main() {
auto autd =
autd3::ControllerBuilder({autd3::AUTD3(autd3::Point3::origin())}).open(autd3::link::Nop::builder());
autd3::Static m;
autd3::Null g;
autd.send((m, g).with_timeout(std::chrono::milliseconds(20)));
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Link;
using AUTD3Sharp.Gain;
using AUTD3Sharp.Modulation;
using AUTD3Sharp.Utils;
using AUTD3Sharp.Driver.Datagram;
using var autd = Controller.Builder([new AUTD3(Point3.Origin)]).Open(Nop.Builder());
var m = new Static();
var g = new Null();
autd.Send((m, g).WithTimeout(Duration.FromMillis(20)));
# mypy: ignore-errors
from pyautd3 import Controller, AUTD3, Static, Null, Duration
from pyautd3.link.audit import Audit
autd = Controller.builder([AUTD3([0.0, 0.0, 0.0])]).open(Audit.builder())
m = Static()
g = Null()
autd.send((m, g).with_timeout(Duration.from_millis(20)))
If the timeout time is greater than 0, the send
function waits until the sent data is processed by the device or the specified timeout time elapses.
If it is confirmed that the sent data has been processed by the device, the send
function returns true
, otherwise it returns false
.
If the timeout time is 0, the send
function does not check whether the sent data has been processed by the device or not.
If you want to data to be sent surely, it is recommended to set this to an appropriate value.
Clear
You can clear the flags and Gain
/Modulation
data in the device by sending Clear
data.
group
You can group the devices by using group
function, and send different data to each group.
use autd3::prelude::*;
#[allow(unused_variables)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut autd = Controller::builder([AUTD3::new(Point3::origin()), AUTD3::new(Point3::origin())]).open(autd3::link::Nop::builder())?;
let x = 0.;
let y = 0.;
let z = 0.;
autd.group(|dev| match dev.idx() {
0 => Some("focus"),
1 => Some("null"),
_ => None,
})
.set("null", Null::new())?
.set("focus", Focus::new(Point3::new(x, y, z)))?
.send()?;
Ok(())
}
#include<chrono>
#include<autd3.hpp>
#include<autd3/link/nop.hpp>
int main() {
auto autd =
autd3::ControllerBuilder({autd3::AUTD3(autd3::Point3::origin())}).open(autd3::link::Nop::builder());
const auto x = 0.0;
const auto y = 0.0;
const auto z = 0.0;
autd.group([](const autd3::Device& dev) -> std::optional<const char*> {
if (dev.idx() == 0) {
return "null";
} else if (dev.idx() == 1) {
return "focus";
} else {
return std::nullopt;
}
})
.set("null", autd3::gain::Null())
.set("focus", autd3::gain::Focus(autd3::Point3(x, y, z)))
.send();
return 0; }
using AUTD3Sharp;
using AUTD3Sharp.Link;
using AUTD3Sharp.Gain;
using AUTD3Sharp.Modulation;
using AUTD3Sharp.Utils;
using var autd = Controller.Builder([new AUTD3(Point3.Origin)]).Open(Nop.Builder());
var x = 0.0f;
var y = 0.0f;
var z = 0.0f;
autd.Group(dev =>
{
return dev.Idx switch
{
0 => "null",
1 => "focus",
_ => null
};
})
.Set("null", new Null())
.Set("focus", new Focus(new Point3(x, y, z)))
.Send();
from pyautd3.gain import Focus, Null
from pyautd3 import Controller, AUTD3, Device
from pyautd3.link.audit import Audit
autd = Controller.builder([AUTD3([0.0, 0.0, 0.0])]).open(Audit.builder())
x = 0.0
y = 0.0
z = 0.0
def grouping(dev: Device) -> str | None:
if dev.idx == 0:
return "null"
if dev.idx == 1:
return "focus"
return None
autd.group(grouping).set("null", Null()).set("focus", Focus([x, y, z])).send()
Unlike gain::Group
, you can use any data that can be sent with send
as a value.
However, you can only group by device.
NOTE: This sample uses a string as a key, but you can use anything that can be used as a key for HashMap.
Advanced examples
Below are some samples for advanced users.
Custom Gain Tutorial
You can create your own Gain
.
Here, we will define a FocalPoint
that generates a single focus just like Focus
.
use autd3::prelude::*;
use autd3::core::derive::*;
#[derive(Gain, Debug)]
pub struct FocalPoint {
pos: Point3,
}
pub struct Context {
pos: Point3,
wavenumber: f32,
}
impl GainContext for Context {
fn calc(&self, tr: &Transducer) -> Drive {
(
Phase::from(-(self.pos - tr.position()).norm() * self.wavenumber * rad),
EmitIntensity::MAX,
)
.into()
}
}
impl GainContextGenerator for FocalPoint {
type Context = Context;
fn generate(&mut self, device: &Device) -> Self::Context {
Context {
pos: self.pos,
wavenumber: device.wavenumber(),
}
}
}
impl Gain for FocalPoint {
type G = FocalPoint;
fn init(
self,
_geometry: &Geometry,
_filter: Option<&HashMap<usize, BitVec>>,
) -> Result<Self::G, GainError> {
Ok(self)
}
}
#[allow(unused_variables)]
fn main() {
}
{{#include ../../../codes/Users_Manual/advanced/custom_gain_0.cpp}}
{{#include ../../../codes/Users_Manual/advanced/custom_gain_0.cs}}
{{#include ../../../codes/Users_Manual/advanced/custom_gain_0.py}}
Custom Modulation Tutorial
You can create your own Modulation
as well as Gain
.
Here, we try to create a Burst
that outputs only for a certain moment in a cycle.
The following is a sample of Burst
.
use autd3::prelude::*;
use autd3::core::derive::*;
#[derive(Modulation, Debug)]
pub struct Burst {
config: SamplingConfig,
loop_behavior: LoopBehavior,
}
impl Burst {
pub fn new() -> Self {
Self {
config: (4000 * Hz).try_into().unwrap(),
loop_behavior: LoopBehavior::infinite(),
}
}
}
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())
}
}
#[allow(unused_variables)]
fn main() {
}
{{#include ../../../codes/Users_Manual/advanced/custom_modulation_0.cpp}}
{{#include ../../../codes/Users_Manual/advanced/custom_modulation_0.cs}}
{{#include ../../../codes/Users_Manual/advanced/custom_modulation_0.py}}
AUTD3 Simulator
AUTD Simulator is a simulator for AUTD3 supporting Windows, Linux, and macOS.
AUTD Server
The simulator is included in AUTD Server
.
Download the installer from GitHub Releases.
When you run AUTD Server
, the following screen will appear, so open the "Simulator" tab and click "Run" button.
When you run the simulator, it is waiting for connection.
In this state, when you open
the Controller
using link::Simulator
, a black panel will appear on the simulator.
This black panel is called "Slice", and this "Slice" can be used to visualize the sound field at an arbitrary position. The phase of the transducer is represented by hue, and its amplitude is represented by intensity.
The sound field displayed in the simulator is a simple superposition of spherical waves; directivity and nonlinear effects are not taken into account.
The GUI displayed on the left side of the screen is used to control "Slice" and the camera.
Slice tab
In the Slice tab, you can change the size, position, and rotation of the slice. The rotation is specified in terms of XYZ Euler angles. The "xy", "yz", and "zx" buttons are used to rotate the slice to a position parallel to each plane.
In the "Color settings" section, you can change the coloring palette, Max pressure. If you use a large number of devices, colors may become saturated, in which case you should increase the value of "Max pressure".
Camera tab
In Camera tab, you can change camera position, rotation, field of view, near clip, and far clip. The rotation is specified in terms of XYZ Euler angles.
Config tab
In the Config tab, you can set the sound speed, font size, and background color.
You can also switch the show/enable/overheat settings for each device. When "show" is turned off, the devices contribute to the sound field only by not being displayed. When "enable" is turned off, it does not contribute to the sound field.
Info tab
In the Info tab, information on FPS, Silencer, Modulation, and STM can be checked.
The Silencer setting can be checked, but it is not affected in the sound field.
When "Mod enable" is set, the modulation is reflected in the sound field.
The index of modulation data and STM is determined by system time. "System time" represents the system time, which is elapsed time in nanoseconds since January 1, 2000, 0:00:00.
When "Auto play" is set, the system time is automatically incremented. Other wise, you can set the system time manually.
FAQ
- "No AUTD3 devices found"
- "One ore more slaves are not responding"
- Frequent send failures when using
link::SOEM
- The link is frequently broken.
- Error when using
link::RemoteTwinCAT
- Miscellaneous
"No AUTD3 devices found"
-
If you use
link::SOEM
on macOS or linux, you need root privileges.-
On linux, you can bypass this by setting the following privileges with the
setcap
command:sudo setcap cap_net_raw,cap_net_admin=eip ./examples/example_soem
-
-
(Windows) Install the latest npcap
-
Virtual machines such as WSL are not supported.
- VirtualBox and other virtual machines may work, but the behavior will be unstable.
"One ore more slaves are not responding"
-
Update the driver
- If you are using Realtek on Windows, please download latest
Win10 Auto Installation Program (NDIS)
driver from official site, and install it.- Even if you use Windows 11, you must use NDIS version.
- If you are using Realtek on Windows, please download latest
-
(Windows) Install the latest npcap.
-
Increase the values of
send_cycle
andsync0_cycle
.
Frequent send failures when using link::SOEM
-
This problem occurs when using the onboard ethernet interface, and one of the following situations
- Using RealSense, Azure Kinect, webcam, etc.
- Basically, the problem occurs when the camera is activated.
- Playing a video or audio file.
- Or, open a video site (e.g. Youtube) with an browser.
- Using Unity
- Playing animation in Blender
- Other operations (modeling, etc.) are fine.
- Using RealSense, Azure Kinect, webcam, etc.
-
As a workaround, try one of the following
- Use
link::TwinCAT
,link::RemoteTwinCAT
, orlink::RemoteSOEM
- Use a USB to Ethernet adapter
- It has been confirmed that at least the adapter using the "ASIX AX88179" chip works properly.
- The same problem may occur with PCIe ethernet adapters.
- Set to
FreeRun
mode - Increase the values of
send_cycle
andsync0_cycle
- In this case, however, the send latency will increase.
- Use Linux or macOS.
- Virtual machines are not acceptable.
- Use
The link is frequently broken.
- If this occurs frequently during ultrasound output, check if there is enough power.
- A single device consumes up to 50W.
Error when using link::RemoteTwinCAT
- It may be blocked by a firewall, turn off the firewall or allow port 48898 of TCP/UDP.
- Disconnect all client PCs from LAN except for the server.
Miscellaneous
- Please feel free to ask questions or report bugs to Issue on Github
Citation
If you use this SDK in your research, please cite the following paper.
- S. Suzuki, S. Inoue, M. Fujiwara, Y. Makino and H. Shinoda, "AUTD3: Scalable Airborne Ultrasound Tactile Display," in IEEE Transactions on Haptics, doi: 10.1109/TOH.2021.3069976.
- S. Inoue, Y. Makino and H. Shinoda "Scalable Architecture for Airborne Ultrasound Tactile Display", Asia Haptics 2016
LICENSE
The autd3 library is MIT licensed.
See LICENSE file of each repository for mode details.
3rdparty library license
Release Notes
Date | Software Version | Firmware Version |
---|---|---|
2025/01/16 | 29.0.0-rc.16 | 10.0.1 |
2024/10/15 | 28.1.0 | 10.0.0 |
2024/10/14 | 28.0.1 | 10.0.0 |
2024/10/12 | 28.0.0 | 10.0.0 |
2024/08/09 | 27.0.0 | 9.0.0 |
2024/06/29 | 26.0.0 | 8.0.1 |
2024/06/18 | 25.3.2 | 8.0.1 |
2024/06/17 | 25.3.1 | 8.0.1 |
2024/06/14 | 25.2.3 | 8.0.1 |
2024/06/10 | 25.1.0 | 8.0.1 |
2024/06/08 | 25.0.1 | 8.0.0 |
2024/06/06 | 25.0.0 | 8.0.0 |
2024/05/22 | 24.1.0 | 7.0.0 |
2024/05/18 | 24.0.0 | 7.0.0 |
2024/05/13 | 23.1.0 | 7.0.0 |
2024/05/11 | 23.0.1 | 7.0.0 |
2024/05/11 | 23.0.0 | 7.0.0 |
2024/04/08 | 22.1.0 | 6.1.x |
2024/03/30 | 22.0.4 | 6.0.x |
2024/02/25 | 22.0.1 | 6.0.x |
2024/01/29 | 21.1.0 | 5.1.x |
2024/01/26 | 21.0.1 | 5.1.x |
2024/01/11 | 20.0.3 | 5.0.x |
2024/01/05 | 20.0.0 | 5.0.x |
2023/12/14 | 19.1.0 | 4.1.x |
2023/12/10 | 19.0.0 | 4.1.x |
2023/12/04 | 18.0.1 | 4.0.x |
2023/12/02 | 18.0.0 | 4.0.x |
2023/11/29 | 17.0.3 | 4.0.x |
2023/11/28 | 17.0.2 | 4.0.x |
2023/11/27 | 17.0.1 | 4.0.x |
2023/11/27 | 17.0.0 | 4.0.x |
2023/10/14 | 16.0.0 | 3.0.x |
2023/10/04 | 15.3.1 | 3.0.x |
2023/10/04 | 15.3.0 | 3.0.x |
2023/09/29 | 15.2.1 | 3.0.x |
2023/09/28 | 15.2.0 | 3.0.x |
2023/09/22 | 15.1.2 | 3.0.x |
2023/09/15 | 15.1.1 | 3.0.x |
2023/09/14 | 15.1.0 | 3.0.x |
2023/09/14 | 15.0.2 | 3.0.x |
2023/08/07 | 14.2.2 | N/A |
2023/08/01 | 14.2.1 | N/A |
2023/07/28 | 14.2.0 | N/A |
2023/07/27 | 14.1.0 | N/A |
2023/07/19 | 14.0.1 | N/A |
2023/07/18 | 14.0.0 | N/A |
2023/07/11 | 13.0.0 | N/A |
2023/07/04 | 12.3.1 | N/A |
2023/07/04 | 12.3.0 | N/A |
2023/06/24 | 12.2.0 | N/A |
2023/06/23 | 12.1.1 | N/A |
2023/06/22 | 12.1.0 | N/A |
2023/06/21 | 12.0.0 | N/A |
2023/06/12 | 11.1.0 | N/A |
2023/06/09 | 11.0.2 | N/A |
2023/06/09 | 11.0.1 | N/A |
2023/06/08 | 11.0.0 | N/A |
2023/05/30 | 10.0.0 | N/A |
2023/05/01 | 9.0.1 | N/A |
2023/04/29 | 9.0.0 | N/A |
2023/04/26 | 8.5.0 | N/A |
2023/04/23 | 8.4.1 | N/A |
2023/04/18 | 8.4.0 | N/A |
2023/03/21 | 8.3.0 | N/A |
2023/03/10 | 8.2.0 | N/A |
2023/02/19 | 8.1.2 | N/A |
2023/02/03 | 8.1.1 | N/A |
2023/02/02 | 8.1.0 | N/A |
2023/01/24 | 2.8.0 | N/A |
2023/01/18 | 2.7.6 | N/A |
2023/01/17 | 2.7.5 | N/A |
2023/01/12 | 2.7.4 | N/A |
2022/12/29 | 2.7.2 | N/A |
2022/12/24 | 2.7.1 | N/A |
2022/12/16 | 2.7.0 | N/A |
2022/12/13 | 2.6.8 | N/A |
2022/12/10 | 2.6.7 | N/A |
2022/12/06 | 2.6.6 | N/A |
2022/11/28 | 2.6.5 | N/A |
2022/11/22 | 2.6.4 | N/A |
2022/11/21 | 2.6.3 | N/A |
2022/11/15 | 2.6.2 | N/A |
2022/11/13 | 2.6.1 | N/A |
2022/11/10 | 2.6.0 | N/A |
2022/11/08 | 2.5.2 | N/A |
2022/11/06 | 2.5.1 | N/A |
2022/11/04 | 2.5.0 | N/A |
2022/10/25 | 2.4.5 | N/A |
2022/10/21 | 2.4.4 | N/A |
2022/10/18 | 2.4.3 | N/A |
2022/10/14 | 2.4.2 | N/A |
2022/10/09 | 2.4.1 | N/A |
2022/09/28 | 2.4.0 | N/A |
2022/08/14 | 2.3.1 | N/A |
2022/08/08 | 2.3.0 | N/A |
2022/06/29 | 2.2.2 | N/A |
2022/06/22 | 2.2.1 | N/A |
2022/06/10 | 2.2.0 | N/A |
2022/06/02 | 2.1.0 | N/A |
2022/05/25 | 2.0.3 | N/A |
2022/05/24 | 2.0.2 | N/A |
2022/05/22 | 2.0.1 | N/A |
2022/05/17 | 2.0.0 | N/A |
Document History
Date | Description |
---|---|
2025/01/16 | Version 29.0.0-rc.16 Initial release |
2024/10/15 | Version 28.1.0 Initial release |
2024/10/14 | Version 28.0.1 Initial release |
2024/10/12 | Version 28.0.0 Initial release |
2024/08/09 | Version 27.0.0 Initial release |
2024/06/29 | Version 26.0.0 Initial release |
2024/06/18 | Version 25.3.2 Initial release |
2024/06/17 | Version 25.3.1 Initial release |
2024/06/14 | Version 25.2.3 Initial release |
2024/06/10 | Version 25.1.0 Initial release |
2024/06/08 | Version 25.0.1 Initial release |
2024/06/06 | Version 25.0.0 Initial release |
2024/05/22 | Version 24.1.0 Initial release |
2024/05/18 | Version 24.0.0 Initial release |
2024/05/13 | Version 23.1.0 Initial release |
2024/05/11 | Version 23.0.1 Initial release |
2024/05/11 | Version 23.0.0 Initial release |
2024/04/08 | Version 22.1.0 Initial release |
2024/03/30 | Version 22.0.4 Initial release |
2024/02/25 | Version 22.0.1 Initial release |
2024/01/29 | Version 21.1.0 Initial release |
2024/01/26 | Version 21.0.1 Initial release |
2024/01/11 | Version 20.0.3 Initial release |
2024/01/05 | Version 20.0.0 Initial release |
2023/12/14 | Version 19.1.0 Initial release |
2023/12/10 | Version 19.0.0 Initial release |
2023/12/04 | Version 18.0.1 Initial release |
2023/12/02 | Version 18.0.0 Initial release |
2023/11/29 | Version 17.0.3 Initial release |
2023/11/28 | Version 17.0.2 Initial release |
2023/10/14 | Version 16.0.0 Initial release |
2023/10/04 | Version 15.3.1 Initial release |
2023/10/04 | Version 15.3.0 Initial release |
2023/09/29 | Version 15.2.1 Initial release |
2023/09/28 | Version 15.2.0 Initial release |
2023/09/22 | Version 15.1.2 Initial release |
2023/09/18 | Version 15.1.1 Update |
2023/09/15 | Version 15.1.1 Initial release |
2023/09/14 | Version 15.1.0 Initial release |
2023/09/14 | Version 15.0.2 Initial release |
2023/08/07 | Version 14.2.2 Initial release |
2023/08/01 | Version 14.2.1 Initial release |
2023/07/28 | Version 14.2.0 Initial release |
2023/07/27 | Version 14.1.0 Initial release |
2023/07/19 | Version 14.0.1 Initial release |
2023/07/11 | Version 13.0.0 Initial release |
2023/07/04 | Version 12.3.1 Initial release |
2023/07/04 | Version 12.3.0 Initial release |
2023/06/24 | Version 12.2.0 Initial release |
2023/06/23 | Version 12.1.1 Initial release |
2023/06/22 | Version 12.1.0 Initial release |
2023/06/12 | Version 11.1.0 Initial release |
2023/05/01 | Version 9.0.1 Initial release |
2023/04/29 | Version 9.0.0 Initial release |
2023/04/26 | Version 8.5.0 Initial release |
2023/04/23 | Version 8.4.1 Initial release |
2023/04/18 | Version 8.4.0 Initial release |
2023/03/21 | Version 8.3.0 Initial release |
2023/03/12 | Version 8.2.0 Initial release |
2023/02/19 | Version 8.1.2 Initial release |
2023/02/03 | Version 8.1.1 Initial release |
2023/02/02 | Version 8.1.0 Initial release |
2023/01/24 | Version 2.8.0 Initial release |
2023/01/18 | Version 2.7.6 Initial release |
2023/01/17 | Version 2.7.5 Initial release |
2023/01/12 | Version 2.7.4 Initial release |
2022/12/29 | Version 2.7.2 Initial release |
2022/12/24 | Version 2.7.1 Initial release |
2022/12/16 | Version 2.7.0 Initial release |
2022/12/10 | Version 2.6.8 Initial release |
2022/12/10 | Version 2.6.7 Initial release |
2022/12/06 | Version 2.6.6 Initial release |
2022/11/28 | Version 2.6.5 Initial release |
2022/11/22 | Version 2.6.4 Initial release |
2022/11/21 | Version 2.6.3 Initial release |
2022/11/15 | Version 2.6.2 Initial release |
2022/11/13 | Version 2.6.1 Initial release |
2022/11/10 | Version 2.6.0 Initial release |
2022/11/08 | Version 2.5.2 Initial release |
2022/11/06 | Version 2.5.1 Initial release |
2022/11/04 | Version 2.5.0 Initial release |
2022/10/25 | Version 2.4.5 Initial release |
2022/10/21 | Version 2.4.4 Initial release |
2022/10/18 | Version 2.4.3 Initial release |