# Connect to and initialize device
p = Ping360()
p.connect_serial('/dev/ttyUSB0', 2000000)
print(f'{p.initialize() = }')
# Set some settings
print(p.set_number_of_samples(500))
print(p.set_gain_setting(1))
print(p.set_transmit_duration(27))
print(p.set_transmit_frequency(750))
def meters_per_sample(ping_message, v_sound=1500):
""" Returns the target distance per sample, in meters.
'ping_message' is the message being analysed.
'v_sound' is the operating speed of sound [m/s]. Default 1500.
"""
# sample_period is in 25ns increments
# time of flight includes there and back, so divide by 2
return v_sound * ping_message.sample_period * 12.5e-9

As far as I recall there’s a bit more logic that goes into configuration. You need to select a scanning distance then base your calculations off that. At minimum we have this in our driver, now you’ll have to modify it as this was taken out of a class, but the logic is sound. You’ll also have to configure the transducer to use these calculated parameters.

def calculate_sample_period(self):
self.sample_period = int((2 * self.range) / (self.number_of_samples * self.speed_of_sound * 25*(10**-9)))
def calculate_transmit_duration(self):
"""
Per firmware engineer:
1. Starting point is TxPulse in usec = ((one-way range in metres) * 8000) / (Velocity of sound in metres per second)
2. Then check that TxPulse is wide enough for currently selected sample interval in usec, i.e.,
if TxPulse < (2.5 * sample interval) then TxPulse = (2.5 * sample interval)
3. Perform limit checking
"""
inital_transmit = round((self.range * 8000) / self.speed_of_sound)
duration = max((2.5 * self.sample_period) / 1000, inital_transmit)
sonar_min_duration = 5
transmit_max = min(self.sample_period*(64*10**6), 500)
self.transmit_duration = max(sonar_min_duration, min(duration, transmit_max))

Calculating your meters per sample should be as simple as dividing your range, in meters of course, by number of samples. I hope this helps!

I’ve previously written this code to visualise Ping360 scans (where each scan is a single image), which may be worth referring to:

import numpy as np
import colorcet as cc
import matplotlib.pyplot as plt
def visualise_scan(ping_messages=None, title=None,
ax=None, show=True, v_sound=1500, indices=None,
points=None, ignore_dist=None):
''' Plot a static image of a sonar scan.
plot is independent of telemetry data, so includes no heading compensation.
'ping_messages' is a list of ping-protocol (auto_/)device_data messages.
'indices' is a list of indices to draw radial lines for, e.g. for displaying
a region of interest.
'points' is a numpy array of (x,y) points to add to the plot, where y points
in the sonar's forwards direction.
'ignore_dist' is a distance in meters to cover in black, generally to hide
the sonar's internal-reflection region.
'''
if ax is None:
fig = plt.figure()
ax = fig.add_subplot(polar=True)
if title:
ax.set_title(title)
settings = ping_messages[0]
sample_sep = settings.sample_period * 12.5e-9 * v_sound
dists = sample_sep * np.arange(len(settings.data))
angles = []
values = []
for message in ping_messages:
angles.append(message.angle)
values.append(np.frombuffer(message.data, dtype=np.uint8))
# convert ping360 gradians into plottable radians
radians = np.unwrap(np.array(angles) * (2 * np.pi / 400))
R, Theta = np.meshgrid(dists, radians)
ax.pcolormesh(Theta, R, values, cmap=cc.m_rainbow, shading='nearest')
if ignore_dist:
theta = (radians[-1] + radians[0]) / 2
width = abs(radians[-1] - radians[0])
ax.bar(theta, ignore_dist, width=width, bottom=0, color='k', alpha=0.5)
if indices:
ax.vlines(radians[indices], 0, dists[-1], linewidth=1, color='k')
if points is not None:
# convert to radians, magnitudes for plotting on polar axes
# subtract a half-turn to de-compensate for angular compensation in
# points (ping360 0-angle is on cable side, 'front' is at 180 degrees)
ax.scatter(np.arctan2(points[:,1], points[:,0]) - np.pi,
np.linalg.norm(points), 1, 'k')
if show:
plt.show()
return ax, radians, dists