Main ❯ Educational projects ❯ ACubes: User’s manual
![]() |
|
|---|---|
| www.unavlab.com support@unavlab.com |
A3S ACubes User’s manual |
ACubes was conceived primarily for use in education and engineering training.
A³S (or ACubes, “Acoustic Cubes”) is a construction kit, a set of basic functional elements that can be used to build prototypes of almost any type of underwater acoustic navigation or communication system:
The “cubes” handle the conversion of digital signals into underwater acoustic signals and back, allowing the user to focus on practical tasks: developing original navigation algorithms, error-correcting coding algorithms, network protocols and interaction schemes.
These purposes generally require neither extreme miniaturization, nor high power, nor record-breaking range — what matters is simplicity, reliability and affordability. These are the principles on which we based this line of devices.
We deliberately leave the list of “cubes” open-ended, as it was intended to be expandable from the outset. It will grow according to application scenarios — for example, assembling a transducer array or a transceiver for measuring slant range.
These are a single-channel (single-frequency) pulse transmitter and a single-frequency receiver.
The A³T transmitter emits pulses of a fixed frequency and duration when the state of its user-controlled digital input changes.
The A³R receiver can detect these pulses and convey information to the user by changing the state of its digital output.
These two devices are designed to be stacked and share a single transceiving transducer, for example, the RT-1.332820-1. This configuration forms a transceiver.
Connecting the receiver output to the transmitter input produces a responder-beacon: the received signal is passed immediately to the transmitter input, causing it to emit a response acoustic signal. The whole assembly works on the “echo” principle. This arrangement allows measurement of, for example, the round-trip signal propagation time between the interrogating device and the responder-beacon, and therefore the slant range.
The connectors mounted along the long edges of the boards form a bus that allows one transmitter and up to 12 receivers to be combined. The outputs of all receivers are then available on the free connector.
This makes it possible to build and study various transducer array configurations. If a 12-element array is insufficient, any number of stacks can be used, each containing up to 12 A³R modules.
A dedicated A³R-CB2 backplane is available for conveniently combining up to 24 receivers. Naturally, any number of these boards can be used.
Two types of transducers are available for use with A³S modules:
For receive-only operation, receiving transducers are recommended because they:
R-1.d3505-1 series transducers:
| The transducers are designed to be easily joined together |
Underwater Communication & Navigation Laboratory offers several models:
| Model | Characteristics |
|---|---|
| RT-1.332820-1 | The most affordable and compact solution |
| RT-2.332820-1 | Suspended transducer for surface equipment (2 piezoelectric elements) |
| RT-1.524525-1 | Model with enhanced sensitivity |
When these rules are followed, the transducers are durable and easy to maintain.
The simplest scenario, using one receiver and one transmitter.
| No. | Name | Quantity | Note |
|---|---|---|---|
| 1 | Module A³R | 1 | |
| 2 | Module A³T | 1 | |
| 3 | Receiving transducer R-1.d3505-1 | 1 | |
| 4 | Transceiving transducer RT-1.332820-1 | 1 | |
| 5 | Microcontroller board (for example, Arduino Nano) | 2 | Can be replaced with a button to trigger transmission |
| 6 | Dupont Female-Female or Male-Female wires, 25+ cm | 4 |
| Connecting the receiving transducer |
| Connecting the transceiving transducer |
| Pin on XS2 | Pin on Arduino Nano |
|---|---|
| 1 (GND) | GND |
| 4 (Transmission trigger) | 10 (D10) |
#define TX_PIN 10 // Transmission trigger pin
#define LED_PIN 13 // Indicator LED
void setup() {
pinMode(TX_PIN, OUTPUT);
pinMode(LED_PIN, OUTPUT);
digitalWrite(TX_PIN, HIGH); // Initial state
}
void loop() {
digitalWrite(TX_PIN, LOW); // Start of transmission
digitalWrite(LED_PIN, HIGH);
delay(10); // Pulse duration 10 ms
digitalWrite(TX_PIN, HIGH); // End of transmission
digitalWrite(LED_PIN, LOW);
delay(990); // Total period 1 second
}
Note: the minimum interval between transmissions is 40 ms
| Pin on XS3 | Pin on Arduino Nano |
|---|---|
| 2 (GND) | GND |
| 1 (Receive strobe) | 2 (D2) |
#define RX_PIN 2 // Reception detection pin
#define LED_PIN 13 // Indicator LED
void setup() {
pinMode(RX_PIN, INPUT);
pinMode(LED_PIN, OUTPUT);
attachInterrupt(digitalPinToInterrupt(RX_PIN), rxDetected, FALLING);
}
void rxDetected() {
digitalWrite(LED_PIN, HIGH);
delay(100); // LED stays on for 100 ms upon reception
digitalWrite(LED_PIN, LOW);
}
void loop() {
// No main loop is needed
}
| Laboratory setup |
Multipath propagation and methods of mitigating it are an extensive field of research. This section covers only the basic principles.
Multipath propagation can be compared to an acoustic echo. A sound signal propagates from its source as a spherical wavefront, with:
For simple signaling systems, the most effective solution is a lockout interval:
| Parameter | Description |
|---|---|
| Operating principle | After detecting a signal, the receiver temporarily stops processing incoming signals |
| Duration | Determined experimentally for the specific conditions |
| Influencing factors | Depth, bottom topography, presence of objects, properties of the water |
Setup recommendations:
Note: professional systems use more sophisticated methods (separate frequencies, signal coding, adaptive algorithms), but these are beyond the scope of this manual.
In this project, we will assemble two transceivers: one will act as a responder-beacon, while the other will emit a request signal, wait for a response and determine the slant range between the subscribers from the signal propagation time and the speed of sound.
| No. | Name | Quantity | Note |
|---|---|---|---|
| 1 | Module A3R | 2 | |
| 2 | Module A3T | 2 | |
| 3 | Transceiving transducer RT-1.332820-1 | 2 | |
| 4 | Any microcontroller board, for example, Arduino Nano | 2 | |
| 5 | LCD display, for example MT-204S | 1 | For displaying the measured time and range; these can also be output to the COM port |
| 6 | Dupont Female-Female or Male-Female wires, 25+ cm | 15 |
This scenario actually includes at least two different subscenarios:
The first involves installing jumper P0 on the responder-beacon so that the receiver output is connected to the transmitter input. The responder-beacon will then emit a response signal with zero delay. This may seem the most convenient and simplest arrangement, but it imposes certain limitations. The receiver modules have a so-called lockout interval that determines how long after reception the next reception is possible. This means the system has a minimum range below which distances cannot be measured: when a response signal arrives from a distance below this minimum, the receiver will still be waiting for the lockout interval to end and will be unresponsive.
In the second subscenario, we introduce a fixed delay between receiving the request signal and emitting the response. This delay is known to the interrogating device and can easily be accounted for. Although this arrangement is more complex, it allows distances down to almost zero to be measured. The most convenient way to generate this delay is to use a microcontroller, such as an Arduino.
Let us start with the interrogating device. Again, we have prepared two options of different complexity: with a display, or without one, sending the necessary information over UART.
First, assemble a “sandwich” of A3R and A3T modules. Set the receiver bus address by installing jumper P1. This places all connections to the Arduino board on connector XS2. Solder the transducer braid and negative lead together. Connect the receiver and transmitter boards with short wire jumpers through their XS1 connectors.
| Connecting a shared transducer to the receiver and transmitter modules |
Next, connect the cube assembly to the Arduino board. This requires 4 male-female wires.
| Connecting the “sandwich” to the Arduino Nano board |
| Pin number/name on XS2 | Pin number/name on Arduino Nano |
|---|---|
| 1 / GND | GND |
| 2 / Transmission start strobe | 3 / INT1 |
| 4 / Pulse transmission trigger | 10 |
| 6 / Receiver No. 1 strobe | 2 / INT0 |
If you plan to use a MELT MT-20S4S LCD display or a compatible one, connect the pins as follows:
| Pin number on the display | Pin number/name on Arduino Nano |
|---|---|
| 1 | GND |
| 2 | 5V |
| 4 | 8 (D8) |
| 5 | 9 (D9) |
| 10 | 4 (D4) |
| 11 | 5 (D5) |
| 12 | 6 (D6) |
| 13 | 7 (D7) |
In addition, connect pins 1 and 5, pins 2 and 18, and pins 2 and 3 on the display board. The resistance between pins 2 and 3 sets the display contrast, and simply shorting them may not be sufficient in some cases.
The sketch for the Arduino control board is shown below:
If you do not plan to use a display, comment out the line #define USE_LCD. The speed of sound is set to a constant of 1500.0; for a more accurate value, use our online speed of sound in water calculator: Proper speed of sound in water calculator
#define USE_LCD
#ifdef USE_LCD
#include "LiquidCrystal.h"
LiquidCrystal lcd(8, 9, 4, 5, 6, 7); // RS, E, D4-D7
#define X_MAX (20) // Number of characters per display line
#define MSG_LINE (3) // Message line number
#define W_LINE (0) // Line number for displaying progress
#endif
// Pin assignments
#define TX_CONTROL_PIN 10 // Transmitter control pin (active HIGH)
#define TX_STROBE_PIN 3 // Transmitter strobe pin (expecting FALLING edge)
#define RX_STROBE_PIN 2 // Receiver strobe pin (expecting FALLING edge)
// System parameters
#define ANSWER_DELAY_MS 500 // Fixed beacon response delay [ms]
#define SOS_MPS 1500 // Speed of sound in water [m/s]
#define MAX_DISTANCE_M 500 // Maximum measured range [m]
#define PULSE_WIDTH_MS 10 // Control pulse duration [ms]
#define PAUSE_MS (1000) // Pause between measurements
#define TIMEOUT (2 * 1000000L * MAX_DISTANCE_M / SOS_MPS + ANSWER_DELAY_MS * 1000L)
#ifdef USE_LCD
#define TKS_PER_CHAR ((TIMEOUT - ANSWER_DELAY_MS * 1000L) / X_MAX)
#endif
// Global variables
volatile uint32_t tor = 0; // Signal emission time
volatile uint32_t toa = 0; // Response signal reception time
volatile bool rx_strobe = false; // Strobe received flag
// Transmitter strobe interrupt handler
void txStrobeISR() {
tor = micros();
}
// Receiver strobe interrupt handler
void rxStrobeISR() {
toa = micros();
rx_strobe = true;
}
void setup() {
#ifdef USE_LCD
lcd.begin(20, 4);
lcd.clear();
lcd.print(F("Starting..."));
#endif
Serial.begin(9600);
Serial.println(F("Starting..."));
// Pin setup
pinMode(TX_CONTROL_PIN, OUTPUT);
digitalWrite(TX_CONTROL_PIN, HIGH);
pinMode(TX_STROBE_PIN, INPUT);
pinMode(RX_STROBE_PIN, INPUT);
// Interrupt setup
attachInterrupt(digitalPinToInterrupt(TX_STROBE_PIN), txStrobeISR, FALLING);
attachInterrupt(digitalPinToInterrupt(RX_STROBE_PIN), rxStrobeISR, FALLING);
delay(1000);
}
void loop() {
#ifdef USE_LCD
lcd.setCursor(0, W_LINE);
lcd.print(" ");
#endif
// 0. Reset
tor = 0;
// 1. Initiate transmission
digitalWrite(TX_CONTROL_PIN, LOW);
// 2. Wait for the transmitter strobe (the interrupt will set tor)
while (tor == 0) {
// Waiting...
}
// 3. Restore the transmission control pin state
digitalWrite(TX_CONTROL_PIN, HIGH);
// 4. Do not process the receiver during the fixed response delay
delay(ANSWER_DELAY_MS);
toa = 0;
rx_strobe = false;
// 5. Wait for the receiver strobe with a timeout
#ifdef USE_LCD
int c_idx = 0;
uint32_t tks = micros();
#endif
while (!rx_strobe && (micros() - tor < TIMEOUT)) {
// Waiting...
#ifdef USE_LCD
if ((micros() - tks) >= TKS_PER_CHAR) {
lcd.setCursor(c_idx, W_LINE);
lcd.print(")");
if (c_idx < X_MAX) c_idx++;
tks = micros();
}
#endif
}
// 6. If a signal was received, calculate the range
if (rx_strobe) {
// Handle micros() overflow correctly
uint32_t tof;
if (toa > tor) {
tof = toa - tor;
} else {
tof = (0xFFFFFFFF - tor) + toa;
}
// Subtract the fixed beacon delay and divide by 2 (round trip)
tof = (tof - ANSWER_DELAY_MS * 1000L) / 2;
// Calculate the range
float srn = tof * 1e-6 * SOS_MPS;
#ifdef USE_LCD
lcd.setCursor(0, MSG_LINE);
lcd.print(" ");
lcd.setCursor(0, MSG_LINE);
lcd.print(srn, 1);
lcd.print(" m");
#endif
Serial.println(srn, 1);
} else {
#ifdef USE_LCD
lcd.setCursor(0, MSG_LINE);
lcd.print(" TIMEOUT ");
#endif
Serial.println("TIMEOUT");
}
// Pause between measurements
delay(1000);
}
We should say up front that this option is almost never used in practice because of the risk of entering a loop in which the beacon “interrogates itself” with its own response signal.
Here we need to assemble the same “sandwich” as for the interrogating device, with the sole difference that we install jumper P0, which connects the receiver output to the transmitter input. The receiver strobe generated upon signal reception becomes the transmission trigger strobe. Also remember to install the jumper that sets the receiver bus address, making its output available on connector XS2.
| Jumper P0 connects the receiver output to the transmitter input |
Connect the transducer in the same way as for the interrogating device: solder the braid and negative lead together and join the receiver and transmitter XS1 connectors with jumpers. Note that for both the interrogating device and the responder-beacon, power must be supplied to the transmitter module to prevent substantial currents from flowing through the bus during transmission.
In this case, set ANSWER_DELAY_MS to a nonzero value in the interrogating device’s Arduino sketch. Both the receiver board and the transmitter board have a lockout interval of 40 ms. Accordingly, the fixed delay you select must exceed this value. In our example, we set it to 500 ms, which is suitable for most small bodies of water with a long “tail” of reflections.
No changes other than the ANSWER_DELAY_MS value are needed in the interrogating device sketch. On the responder-beacon, remove jumper P0 and connect a second Arduino Nano board according to the following table:
| Pin number/name on XS2 | Pin number/name on Arduino Nano |
|---|---|
| 1 / GND | GND |
| 4 / Pulse transmission trigger | 10 |
| 6 / Receiver No. 1 strobe | 2 / INT0 |
A simple sketch that waits for signal reception, pauses and emits a response signal could look like this:
#define A3R_STATE_PIN (2)
#define A3T_TX_ENGAGE_PIN (10)
#define LED_PIN (13)
#define ANSWER_DELAY_MS (500L) // Fixed response delay at the responder, [ms]
#define DEAD_TIME_MS (500L) // Lockout interval after transmission
#define TX_STROBE_DURATION_MS (10L)
void setup() {
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
pinMode(A3T_TX_ENGAGE_PIN, OUTPUT);
digitalWrite(A3T_TX_ENGAGE_PIN, HIGH);
pinMode(A3R_STATE_PIN, INPUT_PULLUP);
}
void loop() {
if (digitalRead(A3R_STATE_PIN) == LOW) {
delay(ANSWER_DELAY_MS);
digitalWrite(A3T_TX_ENGAGE_PIN, LOW);
digitalWrite(LED_PIN, HIGH);
delay(TX_STROBE_DURATION_MS);
digitalWrite(A3T_TX_ENGAGE_PIN, HIGH);
delay(DEAD_TIME_MS);
digitalWrite(LED_PIN, LOW);
}
}
When working with piezoceramic transducers, always keep in mind that they are typically driven at tens to hundreds of volts, at frequencies of kilohertz to tens of kilohertz. This combination can mean that, on the bench or more generally in air, underwater acoustic systems operate electromagnetically rather than acoustically. In actual air tests, we encounter both effects: acoustic and electromagnetic.
Why, then, might we need a bench test, a “bench” test, or even a “bench test”? Besides verifying all connections and confirming that the prototype works as a whole, it makes sense to determine the static error and its statistical parameters. Its causes may vary: clock inaccuracy, hardware limitations, limitations associated with the signal type and processing methods, or perhaps something we forgot or estimated incorrectly, and so on.
For example, our test prototype produces the following propagation times when the transducers are in air and touching each other, i.e., when the actual distance is zero:
| No. | TOF, s |
|---|---|
| 1 | 0.001054 |
| 2 | 0.001182 |
| 3 | 0.001024 |
| 4 | 0.001052 |
| 5 | 0.001148 |
| 6 | 0.001174 |
| 7 | 0.000966 |
| 8 | 0.001154 |
| 9 | 0.001004 |
| 10 | 0.001044 |
| 11 | 0.001282 |
| 12 | 0.001170 |
The mean propagation time over 150 measurements is 0.001096 s, equivalent to a distance of 1.64 m at a speed of sound of 1500 m/s. TOF varies from 0.000886 to 0.001308 s, or from 1.33 to 1.96 m, respectively.
The distribution of the static error is interesting: it closely resembles a so-called Gaussian mixture, i.e., a mixture of normal (Gaussian) distributions. Multimodality may indicate that the process follows several different scenarios.
| Multimodality in the static error distribution |
FOR INDEPENDENT STUDY: Identifying and eliminating the causes of this static error is an interesting and engaging task, and we cannot deprive the reader of the pleasure of doing it independently.
For simplicity, we will account for the measured static error by subtracting its mean value: introduce #define MAGIC_STATIC_S (0.001096253) and subtract this value when calculating slant range:
float srn = ((tof * 1e-6) - MAGIC_STATIC_S) * SOS_MPS;
To summarize, our “bench” experiments have:
We can now move on to experiments in water.
After debugging the prototype on the bench, you can try any container of water or body of water. You can even start with a plastic bucket, as long as it can hold water and two transducers.
Here, for example, is one possible “laboratory setup”. Its transducers are 40 cm apart.
| “Laboratory setup” |
The distribution of the measured range now looks somewhat different:
| Distribution of the measured propagation time |
The sample size does not allow definite conclusions, but there is a hint of a normal distribution. We suggest that users test this hypothesis themselves by increasing the sample size. If this hypothesis is confirmed, it will cast the multimodality of the distribution obtained from measurements in air in a somewhat different light.
Converting time to range gives a range for this experiment from effectively zero, -0.08 m, to 2.07 m. The mean and mode are 0.58 m and 0.52 m, respectively. This differs somewhat from the actual distance of 0.4 m between the transducers, but high accuracy and repeatability should not be expected in such a small volume.
To summarize:
Even a small swimming pool with a volume of ~100 m3 offers an opportunity for more thorough quantitative evaluation of the prototype equipment.
| Good agreement between direct and acoustic measurements |
The transducers can be placed along the same wall. Clearly, larger pools with a soft polymer lining are more favorable for acoustic experiments. During the experiments, measurements were taken from 0.8 to 8 meters at fixed intervals of 0.8 m, corresponding to the width of the coping tiles around the pool.
| Range measurement result at an actual distance of 8 meters between the transducers |
The distribution of the measured propagation time, based on 150 measurements, looks like this:
| Presumably bimodal distribution of the measured propagation time. Sample size 150 |
Again, the distribution appears multimodal, or even bimodal. All measurements fall between 7.8 and 8.9 m, and the mean of 8.2 agrees well with the actual distance of 8 m.
FOR INDEPENDENT STUDY: Note that with a multimodal distribution, discussing the mean of the entire sample is not entirely appropriate. We also suggest that the user explore this issue independently.
To summarize:
Recall that the maximum practical range of the A³R and A³T cubes is 300 m according to the specifications. Naturally, this does not mean that the equipment will achieve its stated performance in any conditions, even the most unfavorable ones; nor does it mean that a previously stable link will suddenly stop working at 301 m.
For example, during a maximum-range test, a transmitter emitting 1 signal per second was placed on an anchored raft, while the receiver was gradually moved away in a rowboat. Noticeable reception interruptions began at a distance of 350–370 meters. The experimental conditions were neither ideal nor very difficult. The body of water was a backwater of the Volga River, about 3 km long and between 400 and 200 meters wide at different locations. It had a sandy bottom, moderate vessel traffic and a substantial amount of metal structures on the bottom.
Experiments with the prototype discussed here were conducted in the same body of water. The responder-beacon was placed on an anchored raft of dense foam, initially about 80 meters from the boat landing, and then moved to a distance of 156 meters. In both cases, the distance was measured with a laser rangefinder.
| Range 80 m. Good agreement between direct and acoustic measurements |
| Range 156 m. Good agreement between direct and acoustic measurements |
It is useful to record signals during experiments in bodies of water. For example, in these tests, the recording hydrophone was placed very close to the interrogating device’s transducer. Below are the recordings and screenshots showing randomly selected fragments containing one “request-response” cycle, for ranges of 80 and 156.
FOR INDEPENDENT STUDY: Readers who wish to do so can analyze the recordings themselves:
10-06-2025, Krasnoarmeysky backwater, Volgograd. A³S slant range, 80 m
10-06-2025, Krasnoarmeysky backwater, Volgograd. A³S slant range, 156 m
| Range 80 m. Recording fragment from the interrogating system’s position |
| Range 156 m. Recording fragment from the interrogating system’s position |
First, a recording gives an idea of the acoustic conditions: the presence of noise and its frequency distribution, reverberation duration, etc. Second, it allows the results to be checked. In these two fragments, the intervals between the leading edges of the request and response signals were 610 and 711 ms, respectively.
Accounting for the fixed response delay of 500 ms, dividing the remainder by two for the round trip, and also accounting for the static error of 1.096 ms determined in section 1.4.5.1. and the speed of sound of 1500 m/s gives the following slant ranges:
((0.610-0.500) / 2 - 0.001096) * 1500 = 80.86 m, and
((0.711-0.500) / 2 - 0.001096) * 1500 = 156.6 m
These agree very well with both the laser rangefinder measurements and the prototype’s display.
The reverberation duration, about a quarter of a second, is also noteworthy:
| A 4-millisecond signal continues to “wander” around the body of water for another 250 milliseconds |
If the fixed delay were shorter than this, the interrogating device could easily mistake its own signal for the responder-beacon’s signal.
FOR INDEPENDENT STUDY: As for statistical analysis of the slant range measurements, we suggest that the user carry it out independently. It would be interesting to compare experiments in different bodies of water and under different conditions: the error, the percentage of successful measurements, the effect of the speed of sound, etc. For example, under ice conditions it is much easier to keep the interrogating device and responder-beacon stationary and to measure the actual distance between them more accurately.
To summarize:
There is some confusion surrounding the word “modem”, especially when underwater acoustic modems are involved. The term “modem” itself derives from MODulator and DEModulator. Today it is more commonly understood to mean a device for transmitting and receiving digital data. The poor English translation “sonar modem” is also often encountered. This is, of course, incorrect, since sonar literally means “sound(sonic) navigation and ranging”, which refers more to sonar detection or echolocation.
Clearly, we will use the “cubes” to build a very simple device for receiving and transmitting digital data. Our signal manipulation capabilities are severely limited: essentially, all we can control is the instant at which a pulse is transmitted. We will base our transmission protocol on that capability.
The transmitted data will be encoded by the delay between two successive pulses. From the previous experiments, we know that a lockout interval is needed; otherwise, the receiver will keep triggering until all reverberation in the body of water has died away. In section 1.4.5.4., we established experimentally that it takes about 200–250 milliseconds for the signal to decay below the receiver’s detection threshold.
Let us start with a minimum interval of 300 milliseconds between two pulses. This means that after receiving the first pulse, the receiver records the time and does not check the line state for 300 milliseconds. For example, to transmit in 8-bit chunks, we need to choose an interval and divide it into 256 values. Zero corresponds to the minimum delay of 300 ms, and the value 256 to the maximum possible delay. After the second, “stop” pulse, another lockout interval is required, and it is highly desirable for it to differ from the first interval. The transmitter module specifications also tell us that one pulse lasts 4 milliseconds, so it makes sense to choose a difference between adjacent values greater than 4 ms. In our experiment, we will use 8 ms, twice the pulse duration.
Let us leave the modem algorithm for a moment and check the equipment.
| No. | Name | Quantity | Note |
|---|---|---|---|
| 1 | Module A3R | 2 | |
| 2 | Module A3T | 2 | |
| 3 | Transceiving transducer RT-1.332820-1 | 2 | |
| 4 | Any microcontroller board, for example, Arduino Nano | 2 | |
| 5 | Dupont Male-Female or Female-Female wires, 15+ cm | 6 |
As in the previous experiments, power supplies for the “cubes” are also required. A 3S pack of Li-Ion or LiFePO4 batteries makes a convenient power source. You will also need two PCs, or one PC that can connect to two Arduino boards. Some terminal software is also needed to send data to the serial port and display data received from it. For example, the ‘Serial monitor’ utility in the Arduino IDE is suitable, but using the same PC to connect both modems will make this difficult. For Windows, you can use our simple uConsole utility.
Since the same capabilities are needed as in the previous experiments, the modem wiring is essentially identical to that of the responder-beacon in section 1.4.4..
For convenience, here is that information again:
| Pin number/name on XS2 | Pin number/name on Arduino Nano |
|---|---|
| 1 / GND | GND |
| 4 / Pulse transmission trigger | 10 |
| 6 / Receiver No. 1 strobe | 2 / INT0 |
Our experimental setup looks like this:
| Two underwater acoustic modems |
As the code shows, the sketch is not very complicated. We set the zero-value interval to 300 ms, the “postfix” lockout interval after the second pulse to 420 ms, and the spacing between adjacent values to exactly twice the pulse duration: 8 ms.
#define A3R_STATE_PIN (2)
#define A3T_TX_ENGAGE_PIN (10)
#define LED_PIN (13)
#define TX_STROBE_DURATION_MS (10L)
#define SS_PRE_DEAD_TIME_MS (300L)
#define SS_POS_DEAD_TIME_MS (420L)
#define SS_TIME_SLOTS (256L)
#define SS_TIME_SLOT_MS (8L)
#define SS_MAX_TIME_MS (SS_TIME_SLOTS * SS_TIME_SLOT_MS + SS_PRE_DEAD_TIME_MS + TX_STROBE_DURATION_MS)
bool receiving = false;
uint32_t r_str_time = 0;
uint32_t r_stp_time = 0;
float r_byte = 256;
void strobe() {
digitalWrite(A3T_TX_ENGAGE_PIN, LOW);
digitalWrite(LED_PIN, HIGH);
delay(TX_STROBE_DURATION_MS);
digitalWrite(A3T_TX_ENGAGE_PIN, HIGH);
digitalWrite(LED_PIN, LOW);
}
void setup() {
Serial.begin(9600);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
pinMode(A3T_TX_ENGAGE_PIN, OUTPUT);
digitalWrite(A3T_TX_ENGAGE_PIN, HIGH);
pinMode(A3R_STATE_PIN, INPUT_PULLUP);
}
void loop() {
while (Serial.available()) {
receiving = false;
uint8_t b = Serial.read();
uint32_t ss_time = SS_PRE_DEAD_TIME_MS + SS_TIME_SLOT_MS * b - TX_STROBE_DURATION_MS;
strobe();
delay(ss_time);
strobe();
delay(SS_POS_DEAD_TIME_MS);
}
int r_state = digitalRead(A3R_STATE_PIN);
if ((r_state == LOW) && !receiving) {
r_str_time = millis();
receiving = true;
delay(SS_PRE_DEAD_TIME_MS);
bool is_timeout = false;
while ((digitalRead(A3R_STATE_PIN) != LOW) && (!is_timeout)) {
is_timeout = millis() - r_str_time > SS_MAX_TIME_MS;
}
if (!is_timeout)
{
r_stp_time = millis();
float duration_ms = r_stp_time - r_str_time - SS_PRE_DEAD_TIME_MS;
r_byte = duration_ms / SS_TIME_SLOT_MS;
if ((duration_ms >= 0) && (r_byte <= 255)) {
Serial.write((uint8_t)round(r_byte));
delay(SS_POS_DEAD_TIME_MS);
}
}
delay(10);
receiving = false;
}
}
The sketch operates very simply: while bytes are available from the serial interface, it reads them and generates the corresponding signal:
The receiver works as follows:
Here is the result we obtained in air, with the transducers about 70 cm apart:
| Communication log between two modems |
The signal can easily be “demodulated” “by hand” from a recording. Here, for example, is a recording made in air:
| Communication log between two modems |
The interval between the pulse edges in the recording is 686 milliseconds. Subtract the fixed 300-millisecond delay and divide by 8: (686 - 300) / 8 = 48.25. Rounding 48.25 gives the transmitted byte value, 48, which is the ASCII code for the digit 0.
FOR INDEPENDENT STUDY:
- Try changing the various timing values to reduce transmission duration and thus increase the data rate.
- It would also be useful to calculate and experimentally determine the resulting data rate.
- In the example above, we obtained a fractional value of 48.25. What would happen to the receiver if only integer operations were used? What if the resulting value were 48.52 instead of 48.25?
To summarize:
If you do not know what sample size to use, you can take the number 216
O. V. Verkhodanov, astrophysicist
Human ears form an array, as do those of other living creatures with hearing. Formally, it is a simple array of just two elements, but the reality is more interesting: the brain performs highly complex, multilevel processing that includes the perception of body dimensions, sound intensity, different frequencies and the Doppler effect, not to mention experience.
Our aim here is not so much to fully understand, but to get a small taste of the principles underlying angle-measuring underwater acoustic direction-finding systems. Through a practical approach, of course.
We will build an angle-measuring system based on an array of 4 (four) receivers. Two would suffice in the limiting case, but as already mentioned, achieving an acceptable result with two “ears” requires at least a reptilian brain, and we have no brain at all. So we will make up for quality with quantity, a common practice in nature, social life and engineering.
Let us outline our plan. First, to determine an acceptable spacing for our “ears”, the transducers, we need to determine how much the signal detection time fluctuates between receivers. Since we are building a ULA (uniform linear array), the spacing between receiving elements must be no less than the fluctuation amplitude. Put simply, if different receivers with transducers placed side by side detect arrival times that differ by about 1 millisecond, spacing the elements less than 1.5 meters apart makes no sense. Second, we need to determine whether the Arduino Nano can read the states of four receiver pins with sufficient speed and accuracy, or whether a more powerful platform is needed.
Now let us determine the equipment required.
| No. | Name | Quantity | Note |
|---|---|---|---|
| 1 | Module A3R | 4 | |
| 2 | Module A3T | 1 | |
| 3 | Transceiving transducer RT-1.332820-1 | 1 | |
| 4 | Receiving transducer R-1.d3505-1 | 4 | |
| 5 | Any microcontroller board, for example, Arduino Nano | 2 | |
| 6 | Dupont Male-Female or Female-Female wires, 15+ cm | 8 |
Naturally, two power supplies and a cable for programming the Arduino and receiving its data will also be needed.
In this project, we will build receiving and transmitting sections. The receiving section will be noticeably more complex, while the transmitting section will be very simple. The transmitter is needed to test and debug the receiver, so we will start with it.
This will probably be the simplest device we build in this course. Its only task is to trigger transmission at a certain time interval. Connect the transmitter board to the Arduino Nano according to the table:
| Pin number/name on XS2 | Pin number/name on Arduino Nano |
|---|---|
| 1 / GND | GND |
| 4 / Pulse transmission trigger | 10 |
| 20 / VCC | Vin |
VERY IMPORTANT! The table above shows the supply voltage output from the transmitter board connected to the Arduino Nano’s Vin pin. Do this only after the board has been programmed and disconnected from the PC!!! Otherwise, it will most likely fail!
Also remember to connect a transceiving transducer to the transmitter board.
The sketch is so simple that we will show it here rather than give it a separate section:
#define A3T_TX_ENGAGE_PIN (10)
#define LED_PIN (13)
#define PING_HALF_PERIOD_MS (2000L)
#define TX_STROBE_DURATION_MS (10L)
void setup() {
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
pinMode(A3T_TX_ENGAGE_PIN, OUTPUT);
digitalWrite(A3T_TX_ENGAGE_PIN, HIGH);
}
void loop() {
delay(PING_HALF_PERIOD_MS);
digitalWrite(A3T_TX_ENGAGE_PIN, LOW);
digitalWrite(LED_PIN, HIGH);
delay(TX_STROBE_DURATION_MS);
digitalWrite(A3T_TX_ENGAGE_PIN, HIGH);
delay(PING_HALF_PERIOD_MS);
digitalWrite(LED_PIN, LOW);
}
Its only task is to pull the transmitter control pin to ground for 10 milliseconds at a specified interval, thereby triggering signal transmission.
We will combine four receiver boards in a stack, doing exactly what they were designed for. Before that, connect the transducers and set the addresses so that the signals from different receivers are routed to separate bus lines.
| Setting receiver addresses with jumpers |
We recommend stacking the boards in forward or reverse order: address 1 at the bottom, then 2 and 3, with address 4 at the top. This makes it easier to avoid mixing up the transducers. We also strongly recommend numbering the transducers, using tags on the cables or writing numbers directly on the transducers with a wax pencil (the marks will rub off in water). Never use a permanent marker: during prolonged contact, the dye may diffuse into the polymer and become extremely difficult to remove.
Next, connect the receiver stack to the Arduino Nano according to the table:
| Pin number/name on XS2 | Pin number/name on Arduino Nano |
|---|---|
| 1 / GND | GND |
| 6 / Receiver No. 1 strobe | 2 |
| 8 / Receiver No. 2 strobe | 3 |
| 10 / Receiver No. 3 strobe | 4 |
| 12 / Receiver No. 4 strobe | 5 |
Here is how ours turned out:
| A stack of four receivers connected to the Arduino Nano board |
The sketch is intended to send four signal arrival times to the PC, one for each receiver. It is important to detect various false triggers. The main criterion for recognizing our signal is that all arrival times fall within a certain window determined by the dimensions of our transducer array.
#include "Limits.h"
#define A3R1_STATE_PIN (2)
#define A3R2_STATE_PIN (3)
#define A3R3_STATE_PIN (4)
#define A3R4_STATE_PIN (5)
#define LED_PIN (13)
const uint8_t inputPinsMasks[] = { B00100000,
B00010000,
B00001000,
B00000100 };
const uint8_t inputPins[] = { A3R1_STATE_PIN, A3R2_STATE_PIN, A3R3_STATE_PIN, A3R4_STATE_PIN };
const int numPins = 4;
bool lastPinState[numPins];
bool pinFallen[numPins];
unsigned long fallTime[numPins];
const unsigned long DETECTION_WINDOW = 2000;
bool is_any_pin = false;
unsigned long pin_minTime = 0;
unsigned long pin_maxTime = 0;
uint8_t fallen_pins = 0;
void checkPinTimes() {
is_any_pin = false;
fallen_pins = 0;
for (int i = 0; i < numPins; i++) {
if (pinFallen[i]) {
fallen_pins++;
if (!is_any_pin) {
is_any_pin = true;
pin_minTime = fallTime[i];
pin_maxTime = pin_minTime;
}
}
}
if (is_any_pin) {
for (int i = 0; i < numPins; i++) {
if (pinFallen[i]) {
if (fallTime[i] > pin_maxTime)
pin_maxTime = fallTime[i];
if (fallTime[i] < pin_minTime)
pin_minTime = fallTime[i];
}
}
}
}
void resetAllFlags() {
for (int i = 0; i < numPins; i++) {
pinFallen[i] = false;
}
}
void setup() {
Serial.begin(9600);
for (int i = 0; i < numPins; i++) {
pinMode(inputPins[i], INPUT_PULLUP);
// lastPinState[i] = digitalRead(inputPins[i]);
lastPinState[i] = (PIND & inputPinsMasks[i]) > 0;
pinFallen[i] = false;
fallTime[i] = 0;
}
}
void loop() {
unsigned long currentTime = micros();
uint8_t cPIND = PIND;
for (int i = 0; i < numPins; i++) {
bool currentState = (cPIND & inputPinsMasks[i]) > 0;
if (!currentState && lastPinState[i]) {
pinFallen[i] = true;
fallTime[i] = currentTime;
}
lastPinState[i] = currentState;
}
checkPinTimes();
if (is_any_pin) {
if (fallen_pins == numPins) {
if ((pin_maxTime - pin_minTime) <= DETECTION_WINDOW) {
/**/
for (int i = 0; i < numPins; i++) {
Serial.print(fallTime[i] - pin_minTime);
Serial.print(", ");
}
/**/
Serial.println();
delay(500);
}
resetAllFlags();
} else {
if (currentTime - pin_minTime > DETECTION_WINDOW * 100) {
resetAllFlags();
delay(100);
}
}
}
}
Looking ahead, we should note that the sketch had to abandon the standard digitalRead functions and access the registers directly for greater speed. This makes the sketch somewhat less flexible.
As mentioned earlier, before building the transducer array, we need to determine how much the signal arrival time fluctuates between receivers. For this, we need half a bucket of water. In the bucket, we can place all receiving transducers very close to the transmitting transducer. This eliminates all effects related to propagation time and lets us assess how differently the receivers behave.
Recall that we need this to determine the dimensions of the transducer array.
Our experimental setup looks like this:
| The bucket does not have to be blue =) |
So:
If everything is assembled correctly, the ‘Serial Monitor’ window will show lines arriving every 4 seconds, each containing 4 comma-separated numbers. The sketch normalizes the arrival times by subtracting the smallest value from each group. A few dozen lines will be sufficient.
For clarity, let us plot these times:
| Most values do not exceed 300 μs |
The graph shows that arrival times generally fluctuate relative to one another within 300–350 microseconds. At a speed of sound of ~1500 m/s, this corresponds to about 0.5 meter.
It is useful and easy to remember that sound travels about 1.5 meters in water in one millisecond.
The accuracy is not spectacular, but it is quite usable.
Based on this measured value, we can choose a spacing of 1 meter between the elements of our transducer array. Of course, the sample is fairly small and there is a good chance that the spread could reach 1 millisecond. But first, we want to understand the principle, and second, we will be able to see such cases when calculating the angles of arrival. For now, we want to avoid complicating matters with an excessively bulky array and to understand that even at this size, most data can be sufficiently accurate.
If you have a simple way to hang 4 receiving transducers at 1-meter intervals, that is great — you will have almost nothing to do. We did not, so we assembled this structure from polypropylene plumbing pipes:
| A simple structure for positioning four receiving transducers a meter apart |
We expected the structure to sit directly on the water (in a swimming pool, of course; taking it to a natural body of water is strongly discouraged), so we added extra buoyancy. In fact, we could have done without it.
Let us estimate the buoyancy of the whole frame. First, weigh it: ours was 1.5 kg. Now estimate a lower bound for the volume of water displaced. We use an upper bound for the weight and a lower bound for the volume. If the mass of this volume of water exceeds the measured weight by a reasonable margin, everything is fine: the structure will float. The calculation is very simple. Since we need the minimum volume, we calculate only the pipe volume, excluding fittings, which increase it.
Our pipe has a diameter of 25 mm, so its cross-sectional area is $πr^2 = 3.1415*0.0125^2=0.000490859 \space m^2$. The total pipe length is 8 m. Multiplying the two gives the displaced water volume: $0.000490859 * 8 = 0.003926875 \space m^3$. At a density of $1000 \space kg/m^3$, this volume of water has a mass of 3.9 kg. The measured weight of the entire structure is only 1.5 kg, so we have 3.9-1.5=2.4 kg of excess buoyancy.
Without modifications, the receiving transducer cables are long enough to suspend the transducers about 15–20 cm below the frame.
| Marks are made with electrical tape |
Lay everything out on the floor to see how it looks assembled and how much spare cable length is available.
| There is practically no spare cable length |
The stiff cables may prevent the transducers from hanging straight. This is not entirely without effect in our case, but it should not contribute much to the overall result. Besides, the user can certainly solve this mechanical issue independently.
For a complete picture, we need data for several relative positions of the transmitter and receiving array.
First, let us define “left” and “right”: transducer No. 1 is on the left and transducer No. 4 is on the right:
| Order of transducers in the array |
At a minimum, use the following positions:
If possible, obtain data for intermediate positions, for example, between “left” and “in front”, and between “in front” and “right”.
Here is our experimental layout:
| No. 1 .. No. 4 are signal source positions; R1 .. R4 are receiving array element positions |
For each relative position of the transducer array and source, we recommend collecting 150–200 measurements.
If the pool water has not settled, the receiving and transmitting transducers can often become covered in air bubbles quite quickly. If the receivers suddenly stop responding during the experiment while all connections are sound and there is no visible reason, look closely at the transducer surfaces: they may be covered in air bubbles. Simply remove the bubbles by hand to restore operation.
It is useful to monitor the experiment: first, ensure data is arriving, and second, have at least a general idea of whether it is plausible.
For example, if the source is directly to the left, the signal should reach transducer No. 1 first, then No. 2, and so on. Our sketch sends arrival times in reverse order, so we expect four successively decreasing numbers, such as 1936, 1284, 724, 0, .
We strongly recommend thinking about the data you expect before conducting the experiment. This is good practice not only for this experiment, but for any experiment — and good practice in life in general.
| Keeping a finger on the pulse |
We obtained data sets for five relative positions of the receiving array and signal source: left, between left and in front, in front, between in front and right, and right. The water temperature in the pool during the experiment was 21.6 °C.
Here are the data, again in CSV (comma-separated values) format:
Having collected the data, it is time for the most interesting part: processing it. Welcome to the next section.
In this section, we will touch briefly on mathematics and trigonometry.
First, consider a simple case in which a plane wavefront is incident on two elements of a transducer array:
| Illustration of a plane wavefront incident on two receivers |
In the illustration above, the wavefront is incident on two receivers at an angle $\alpha$, with a delay $\Delta t$ between its arrival at the first receiver (left) and the second (right). If the propagation speed is $V$ and the spacing between receivers is $\Delta X$, then the angle $\alpha$ can be determined from:
\[\frac{\Delta t * V}{\Delta X} = cos(\alpha)\]Cosine is the ratio of the adjacent side to the hypotenuse. In our case, the hypotenuse is $\Delta X$ and the adjacent side is $\Delta t * V$.
The KO MNE (TOWARD ME) rule:
It is easy to remember cosine: COsine is the ratio of the adjacent side (TOWARD ME) to the hypotenuse. Sine is therefore the ratio of the opposite side to the hypotenuse, COtangent is the ratio of the adjacent side (TOWARD ME) to the opposite side, and tangent is the ratio of the opposite side to the adjacent side.
This relationship alone is enough to build an angle-measuring system with two receivers. As noted above, however, this is not advisable: a time measurement error at even one receiver will produce a completely incorrect result. In such cases, it makes sense to increase the number of receivers and let each have a say.
First, assume that a plane wavefront is incident on our array. Viewed from above, it is a line. This is a reasonable assumption if the distance to the source is much greater than the array size. Of course, this does not hold in the pool, but we have to start somewhere, right?
| Determining the angle of arrival at a linear transducer array |
The diagram above shows a coordinate system centered on the first receiver. The X axis points to the right as usual, while the Y axis represents time multiplied by the wave propagation speed.
Now suppose we have taken a measurement and obtained signal arrival times at each receiver. We mark each receiver’s time multiplied by the speed of sound in water with a green circle.
Here is a very important point: if we assume a plane wavefront, the green circles should form a line. The wavefront reaches each receiver after a time proportional to that receiver’s distance from the origin and to the angle at which the wavefront is incident on the linear array.
But there is always a “but”, isn’t there?
The circles do not form a perfectly straight line: there is always some measurement error. However, we need to account for the contribution of every propagation time. Essentially, we must position a line as close as possible to each green circle — as close as possible in the mathematical sense.
In 1929, Edwin Hubble formulated his famous law in precisely this way, drawing a line through a set of measurements.
| Graph from Hubble’s original 1929 paper |
Hubble also obtained noisy measurements and plotted points with the distance to Cepheids in nearby galaxies on the X axis and their velocity on the Y axis. We must give Hubble credit: it takes considerable courage to draw a line through such a cloud of points and insist that the relationship between recession velocity and distance is linear.
Our task is much easier: we already know there should be a line; we just need to draw it correctly, approximating the set of points with a line.
The good news is that this is not difficult: drawing a line means finding its equation. The equation of a line is simple and has the general form $y=kx+b$. We therefore need to determine the coefficients $k$ and $b$, although, looking ahead, we do not really need $b$.
The coefficient $k$ is determined by:
\[k = \frac {n \sum{x_i y_i} - \sum{x_i} \sum{y_i} } {n \sum {x^2_i} - (\sum {x_i})^2}\]$x_i$ are the coordinates of our receivers: 0 for the first, 1 for the second, since we agreed on a spacing of 1 meter. $y_i$ are the measured arrival times multiplied by the speed of sound in water.
Another very important point: the coefficient $k$ is the tangent of the line’s angle of inclination relative to the X axis. Look again at the diagram above:
\[tg \phi = \frac{\Delta t * V}{\Delta X}\]And we recall that
\[\frac{\Delta t * V}{\Delta X} = cos(\alpha)\]This gives the interesting result that:
\[tg \phi = cos \alpha = \frac{\Delta t * V}{\Delta X}\]From this, we can easily calculate $\alpha$, the angle of arrival. Once again, here is the entire sequence of measurements and calculations:
It is difficult to present all these data in an Excel chart or something similar, so we will use Matlab, or rather its free counterpart, GNU Octave.
It is convenient to save the data for processing in separate files “as is”, in CSV format.
We will save them as data1.csv, data2.csv, data3.csv, data4.csv and data5.csv.
We have prepared the following script to process them:
clc;
clear all;
close all;
% Processing parameters
Num = 5; % Data set number (1-5)
% Load data
data_1 = csvread('data1.csv');
data_2 = csvread('data2.csv');
data_3 = csvread('data3.csv');
data_4 = csvread('data4.csv');
data_5 = csvread('data5.csv');
actual_angles = [180, 135, 90, 45, 0]; % Actual angles for each data set
% Select data for processing
if (Num == 1) src_data = data_1;
elseif (Num == 2) src_data = data_2;
elseif (Num == 3) src_data = data_3;
elseif (Num == 4) src_data = data_4;
elseif (Num == 5) src_data = data_5;
endif
% Physical parameters
v = 1503.6; % Speed of sound in water, m/s
t_scale = 1 / 1000000; % Time scale (microseconds to seconds)
% Coordinates of the transducer array receivers (ULA)
ula4_xs = [3 2 1 0];
ula4_ys = [0 0 0 0];
% Create one window with two subplots
figure('Position', [100, 100, 1200, 600]); % Larger window size
%% First plot: measurements and approximation
subplot(1, 2, 1); % 1 row, 2 columns, position 1
axis equal
hold on
grid on
% Preliminary calculations for linear regression
x_sum = sum(ula4_xs);
x2_sum = sum(ula4_xs .^ 2);
x_sum2 = x_sum ^ 2;
rec_num = length(ula4_xs);
% Initialize arrays
k = zeros(length(src_data), 1); % Slope coefficients
b = zeros(length(src_data), 1); % Intercepts
alpha = zeros(length(src_data), 1); % Angles of arrival
% Process each measurement
for n = 1:length(src_data)
% Convert time delays to distances
ys = src_data(n, :) .* t_scale .* v;
% Check data validity
if ~all(isfinite(ys))
warning('Invalid data detected in row %d. Skipping.', n);
continue;
end
% Plot measurements
if n == 1
plot(ula4_xs, ys, 'b.', 'DisplayName', 'Measurements V·t', 'MarkerSize', 4);
else
plot(ula4_xs, ys, 'b.', 'HandleVisibility', 'off', 'MarkerSize', 4);
end
% Calculate line coefficients (least squares method)
y_sum = sum(ys);
xy_sum = sum(ula4_xs .* ys);
denominator = (rec_num * x2_sum - x_sum2);
if denominator == 0
warning('Degenerate system in row %d. Skipping.', n);
continue;
end
k(n) = (rec_num * xy_sum - x_sum * y_sum) / denominator;
b(n) = (y_sum - k(n) * x_sum) / rec_num;
% Plot the fitted line
x_range = [0, 3];
y_range = k(n) * x_range + b(n);
if n == 1
line(x_range, y_range, 'Color', 'red', 'LineWidth', 0.5, 'DisplayName', 'Linear approximation');
else
line(x_range, y_range, 'Color', 'red', 'LineWidth', 0.5, 'HandleVisibility', 'off');
end
% Calculate the angle of arrival using arccosine
k_bound = max(min(k(n), 1), -1); % Clamp to the domain of acos
alpha(n) = acos(-k_bound);
end
% Display receiver positions
plot(ula4_xs, ula4_ys, 'go', 'MarkerSize', 8, 'MarkerFaceColor', 'g', 'DisplayName', 'Array elements');
% Configure the first plot
tstr = sprintf("Linear approximation tgφ=cosα\nActual angle: %d°, sample: %d",...
actual_angles(Num), length(src_data));
title(tstr, "fontsize", 14);
xlabel('X, m', "fontsize", 14);
ylabel('V·t, m', "fontsize", 14);
lh = legend('show');
set(lh, "fontsize", 12);
%% Second plot: angle histogram
subplot(1, 2, 2); % 1 row, 2 columns, position 2
alpha_deg = rad2deg(alpha); % Convert radians to degrees
% Angle statistics
mean_alpha = mean(alpha_deg);
std_alpha = std(alpha_deg);
max_alpha = max(alpha_deg);
min_alpha = min(alpha_deg);
% Plot the histogram
nbins = 20;
[counts, bins] = hist(alpha_deg, nbins);
hist(alpha_deg, nbins, 'DisplayName', 'α, °');
colormap(summer());
hold on;
y_limits = ylim;
% Statistical lines on the histogram
plot([mean_alpha, mean_alpha], y_limits, 'r-', 'LineWidth', 2,...
'DisplayName', sprintf('Mean = %.1f°', mean_alpha));
plot([mean_alpha - std_alpha, mean_alpha - std_alpha], y_limits, 'g--',...
'LineWidth', 1.5, 'DisplayName', sprintf('±1 SD (%.1f°)', std_alpha));
plot([mean_alpha + std_alpha, mean_alpha + std_alpha], y_limits, 'g--',...
'LineWidth', 1.5, 'HandleVisibility', 'off');
% Configure the second plot
llh = legend('show');
set(llh, "fontsize", 12);
tstr = sprintf("Angle of arrival estimation\nActual angle: %d°, sample: %d",...
actual_angles(Num), length(src_data));
title(tstr, "fontsize", 14);
xlabel('Angle α, °', "fontsize", 14);
ylabel('Frequency', "fontsize", 14);
% Print statistics to the console
fprintf('\n=== ANGLE STATISTICS ===\n');
fprintf('Mean: %.2f°\n', mean_alpha);
fprintf('SD: %.2f°\n', std_alpha);
fprintf('Minimum: %.2f°\n', min_alpha);
fprintf('Maximum: %.2f°\n', max_alpha);
fprintf('Range: %.2f°\n', max_alpha - min_alpha);
A couple of reminders:
Now let us look at the results.
For each relative position of the transducer array and source, we obtain two plots: one showing the measured arrival times and a line approximating these measurements, and the other a histogram of the calculated angle of arrival.
For an angle of 180°, the source is directly to the left of the array:
| Actual angle 180° |
| Actual angle 135° |
| Actual angle 90° |
| Actual angle 45° |
| Actual angle 0° |
From the statistics we obtained, we can conclude that the prototype is fully functional overall, especially considering that a small pool is an extremely challenging “body of water” for such systems.
We can see that the standard deviation of the angle-of-arrival estimate is, on average, about 6°. The sample mean corresponds to the actual angle in every experiment except experiment No. 2 (135°). Particularly good results were obtained at 90° and 45°.
How can this system be improved? First, ensure that the array elements (receivers) remain fixed by extending their cables and improving the frame. Second, it would be interesting to see the results under more favorable conditions, such as in a small natural body of water.
We suggest that advanced users explore the following issue independently: in our experiments, we assumed a plane wavefront. In reality, this assumption is valid only for angles of 180° and 0°, when the source lies on the array axis. Looking at the measurement plot for 90°, it is obvious that a circular arc would fit better than a line. Yes, this is more of a double-star challenge for beginners, but successfully solving it will earn you a well-deserved karma point.
Let us summarize:
Let us congratulate and praise ourselves for completing one of the most difficult projects in this manual — you are magnificent!
In this project, we will determine the geographic position — latitude and longitude — of a stationary underwater object. For this, we will build two transceivers, already familiar from 1.4. Project 2 - Assembling a transceiver and measuring slant range. One will be the “underwater” object whose position we want to determine, and the other will be part of the measuring device.
How will we determine the object’s geographic position — its latitude and longitude?
Recall that a measured range places the object on a sphere. If the vertical coordinate is known, it places it on a circle whose radius is determined by the measured range. By measuring range from several points, we “draw” several such circles. The target object (the one we are looking for) will be at their intersection.
Now imagine measuring the range to an object from two different points, for example, from two ships or from the same ship at different times. Each measurement defines its own circle:
If the two circles touch at one point, that is ideal: the object is exactly there. If they intersect at two points, we do not know which one is correct. Another measurement (another circle) is needed to choose the correct location.
In practice, measurements are always inaccurate, so the circles may not intersect at all — for example, if we used an underestimated speed of sound. We then seek a point that is “almost” equally distant from all the circles, i.e., the best approximation.
Such problems are called optimization (or minimization) problems: we seek the object position that best agrees with all measurements.
Each pair of measurements from two different points forms a line: a measurement baseline, or simply a baseline. In our case, however, it is virtual rather than physical: measurements are taken sequentially, not simultaneously, from the same moving ship.
This method works only if the object is stationary. If it moves, it will change position while the ship moves to the next point, and we will measure distances to different positions of the object. The circles will then fail to converge, preventing correct positioning.
Clearly, we need two transceivers: we must interrogate the object whose position we want to determine; it must receive the request and then emit a response signal, which must be received by the interrogating or measuring device.
To establish a geographic reference, we need a GNSS receiver. We also need somewhere to output the results. At the simplest level, this is just a serial port connected to a PC; a display can also be used, or the measuring device can additionally be equipped with a radio modem.
Here is the list of what we need:
| No. | Name | Quantity | Note |
|---|---|---|---|
| 1 | Module A3R | 2 | |
| 2 | Module A3T | 2 | |
| 3 | Transceiving transducer RT-1.332820-1 | 2 | |
| 4 | Any microcontroller board, for example, Arduino Nano | 2 | |
| 5 | GNSS receiver, almost any model that can be connected to the microcontroller board used | 1 | |
| 6 | 433 MHz radio module, for example HC-12 or compatible | 2 | Optional |
| 7 | LCD display, for example MT-204S | 1 | Optional |
| 7 | Dupont Male-Female or Female-Female wires, 15+ cm | 8 |
If you plan to build the version with the minimum functionality, the display and radio module are not required. All system results can then be output to the PC through the serial port.