Skip to content

Commit c871a5c

Browse files
Add TVGAIN support and fine-tune the gain/threshold values (#390)
* Add TVGAIN support and fine-tune the gain/threshold values * Add tool to debug the gain, timing and threshold values for the PGA460 sensor Applied this one to the community pro before sending it out
1 parent 7a07702 commit c871a5c

File tree

3 files changed

+241
-15
lines changed

3 files changed

+241
-15
lines changed

src/pgaSensor.cpp

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -153,25 +153,44 @@ void PGASensorManager::setupSensor(int sensorId)
153153
spiRegWrite(sensorId, PGA_REG_CURR_LIM_P1, PGA_DIS_CL(0) | PGA_CURR_LIM1(0)); // CURR_LIM1*7mA + 50mA
154154
spiRegWrite(sensorId, PGA_REG_CURR_LIM_P2, PGA_LPF_CO(0) | PGA_CURR_LIM2(0)); // CURR_LIM1*7mA + 50mA
155155
spiRegWrite(sensorId, PGA_REG_REC_LENGTH, PGA_P1_REC(8) | PGA_P2_REC(0)); // Record time = 4.096 × (Px_REC + 1) [ms], 8 = 36,9ms = 6m range
156-
spiRegWrite(sensorId, PGA_REG_DECPL_TEMP, PGA_AFE_GAIN_RNG(1) | PGA_LPM_EN(0) | PGA_DECPL_TEMP_SEL(0) | PGA_DECPL_T(0)); // Time = 4096 × (DECPL_T + 1) [μs], 0 = 4ms = 0,66m?!?
156+
spiRegWrite(sensorId, PGA_REG_DECPL_TEMP, PGA_AFE_GAIN_RNG(3) | PGA_LPM_EN(0) | PGA_DECPL_TEMP_SEL(0) | PGA_DECPL_T(0)); // Time = 4096 × (DECPL_T + 1) [μs], 0 = 4ms = 0,66m?!?
157157
spiRegWrite(sensorId, PGA_REG_EE_CNTRL, PGA_DATADUMP_EN(0)); // Disable data dump
158158

159+
// Gain map
160+
// Gain = 0.5 × (TVGAIN+1) + value(AFE_GAIN_RNG) [dB]
161+
// TVGAIN is 0..63, time is any TH_TIME_DELTA_*
162+
// The gain map is applied for both profiles.
163+
// Times are given in deltas to previous time, except the first one
164+
// So there is a constant gain of g0 from 0 to t0, then a linear interpolation from g0 to g1 between t0 and t1,
165+
// then another linear interpolation from g1 to g2 between t1 and t2, and so on. After t6, the gain is constant at g6.
166+
// I think the INIT_GAIN value is ignored, if TVGAIN is used, but AFE_GAIN_RNG still applies.
167+
PGATVGain gains(
168+
TH_TIME_DELTA_2000US, 0,
169+
TH_TIME_DELTA_5200US, 40,
170+
TH_TIME_DELTA_5200US, 45,
171+
TH_TIME_DELTA_5200US, 50,
172+
TH_TIME_DELTA_5200US, 55,
173+
TH_TIME_DELTA_5200US, 60
174+
);
175+
spiRegWriteGains(sensorId, gains);
176+
159177
// Threshold map, then check DEV_STAT0.THR_CRC_ERR
160-
// th_t = np.array([2000, 5200, 2400, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000])
161-
// th_l = np.array([200, 80, 16, 16, 16, 24, 24, 24, 24, 24, 24, 24])
178+
// The first 8 values are only 5 bit, so the 0..255 value has to be devided by 8.
179+
// th_t = np.array([1000, 2000, 2400, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000])
180+
// th_l = np.array([255, 80, 30, 30, 30, 25, 25, 20, 20, 20, 20, 20])
162181
PGAThresholds thresholds(
163-
TH_TIME_DELTA_2000US, 255/8,
164-
TH_TIME_DELTA_4000US, 80/8,
165-
TH_TIME_DELTA_2400US, 24/8,
166-
TH_TIME_DELTA_8000US, 24/8,
167-
TH_TIME_DELTA_8000US, 24/8,
168-
TH_TIME_DELTA_8000US, 32/8,
169-
TH_TIME_DELTA_8000US, 32/8,
170-
TH_TIME_DELTA_8000US, 32/8,
171-
TH_TIME_DELTA_8000US, 32,
172-
TH_TIME_DELTA_8000US, 32,
173-
TH_TIME_DELTA_8000US, 32,
174-
TH_TIME_DELTA_8000US, 32
182+
TH_TIME_DELTA_1000US, 240/8,
183+
TH_TIME_DELTA_2000US, 80/8,
184+
TH_TIME_DELTA_2400US, 30/8,
185+
TH_TIME_DELTA_8000US, 30/8,
186+
TH_TIME_DELTA_8000US, 30/8,
187+
TH_TIME_DELTA_8000US, 25/8,
188+
TH_TIME_DELTA_8000US, 25/8,
189+
TH_TIME_DELTA_8000US, 20/8,
190+
TH_TIME_DELTA_8000US, 20,
191+
TH_TIME_DELTA_8000US, 20,
192+
TH_TIME_DELTA_8000US, 20,
193+
TH_TIME_DELTA_8000US, 20
175194
);
176195
spiRegWriteThesholds(sensorId, 1, thresholds);
177196
spiRegWriteThesholds(sensorId, 2, thresholds);
@@ -267,6 +286,20 @@ void PGASensorManager::spiRegWrite(uint8_t sensorId, uint8_t reg_addr, uint8_t v
267286
safe_usleep(5);
268287
}
269288

289+
void PGASensorManager::spiRegWriteGains(uint8_t sensorId, PGATVGain &gains)
290+
{
291+
assert(sensorId >= 0 && sensorId <= 1);
292+
293+
// The order of the time/gain values is a bit messy, so we have to do some bit shifting to write them to the registers
294+
spiRegWrite(sensorId, PGA_REG_TVGAIN0, gains.t0<<4 | gains.t1);
295+
spiRegWrite(sensorId, PGA_REG_TVGAIN1, gains.t2<<4 | gains.t3);
296+
spiRegWrite(sensorId, PGA_REG_TVGAIN2, gains.t4<<4 | gains.t5);
297+
spiRegWrite(sensorId, PGA_REG_TVGAIN3, gains.g1<<2 | gains.g2>>4);
298+
spiRegWrite(sensorId, PGA_REG_TVGAIN4, gains.g2<<4 | gains.g3>>2);
299+
spiRegWrite(sensorId, PGA_REG_TVGAIN5, gains.g3<<6 | gains.g4);
300+
spiRegWrite(sensorId, PGA_REG_TVGAIN6, gains.g5<<2);
301+
}
302+
270303
void PGASensorManager::spiRegWriteThesholds(uint8_t sensorId, uint8_t preset, PGAThresholds &thresholds)
271304
{
272305
assert(sensorId >= 0 && sensorId <= 1);

src/pgaSensor.h

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,44 @@ struct PGAThresholds
260260
uint8_t l12; // 8(!) bits
261261
};
262262

263+
struct PGATVGain
264+
{
265+
PGATVGain(
266+
uint8_t t0 = TH_TIME_DELTA_100US, uint8_t g0 = 0,
267+
uint8_t t1 = TH_TIME_DELTA_100US, uint8_t g1 = 0,
268+
uint8_t t2 = TH_TIME_DELTA_100US, uint8_t g2 = 0,
269+
uint8_t t3 = TH_TIME_DELTA_100US, uint8_t g3 = 0,
270+
uint8_t t4 = TH_TIME_DELTA_100US, uint8_t g4 = 0,
271+
uint8_t t5 = TH_TIME_DELTA_100US, uint8_t g5 = 0
272+
)
273+
{
274+
this->t0 = t0 & 0x0f;
275+
this->g0 = g0 & 0x3f;
276+
this->t1 = t1 & 0x0f;
277+
this->g1 = g1 & 0x3f;
278+
this->t2 = t2 & 0x0f;
279+
this->g2 = g2 & 0x3f;
280+
this->t3 = t3 & 0x0f;
281+
this->g3 = g3 & 0x3f;
282+
this->t4 = t4 & 0x0f;
283+
this->g4 = g4 & 0x3f;
284+
this->t5 = t5 & 0x0f;
285+
this->g5 = g5 & 0x3f;
286+
}
287+
uint8_t t0;
288+
uint8_t g0;
289+
uint8_t t1;
290+
uint8_t g1;
291+
uint8_t t2;
292+
uint8_t g2;
293+
uint8_t t3;
294+
uint8_t g3;
295+
uint8_t t4;
296+
uint8_t g4;
297+
uint8_t t5;
298+
uint8_t g5;
299+
};
300+
263301
class PGASensorManager
264302
{
265303
public:
@@ -302,6 +340,7 @@ class PGASensorManager
302340
uint8_t spiTransfer(uint8_t sensorId, uint8_t data_out);
303341
int spiRegRead(uint8_t sensorId, uint8_t reg_addr, uint8_t *pdiag = nullptr);
304342
void spiRegWrite(uint8_t sensorId, uint8_t reg_addr, uint8_t value);
343+
void spiRegWriteGains(uint8_t sensorId, PGATVGain &gains);
305344
void spiRegWriteThesholds(uint8_t sensorId, uint8_t preset, PGAThresholds &thresholds);
306345
void spiBurstAndListen(uint8_t sensorId, uint8_t preset, uint8_t numberOfObjectsToDetect);
307346
bool spiUSResult(uint8_t sensorId, uint8_t numberOfObjectsToDetect, PGAResult *usResults);

tools/plot_pga_dump.py

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
# This is a small script to debug the OBSPro ultrasonic sensors (based on the PGA460 chip).
2+
# Its focus is on optimizing the time-variable-gain (TVGAIN), threshold values and timings.
3+
# To use it you need to rebuild the firmware with the PGA_DUMP_ENABLE define set to 1.
4+
# You can find this value in the src/pgaSensor.h file.
5+
# Once enabled, the pgaSensor.cpp will print out the raw PGA values for each sensor in a
6+
# comma-separated format over the USB-serial line. The output will look like this:
7+
# dump,0,123,45,67,89,23,45,67,...
8+
# dump,1,234,56,78,90,34,56,78,...
9+
# meas,0,1234,345,56
10+
# meas,1,2345,678,67
11+
# First value after dump/meas is always the sensor ID (0 or 1).
12+
# The "dump" line contains the raw PGA values over time (128 values).
13+
# The "meas" line contains: time-of-flight [µs], peak width [µs], peak amplitude
14+
# Note that the measurement will probably never exactly match the dump values, because the
15+
# dump and measurement lines are taken from different measurements. Also the measurement
16+
# value is updated much more frequently than the dump values.
17+
# Requirements and setup:
18+
# - python3 -m venv .venv
19+
# - source .venv/bin/activate
20+
# - pip install pyserial matplotlib numpy
21+
22+
import time
23+
import matplotlib.pyplot as plt
24+
import numpy as np
25+
import serial
26+
27+
PORT = "/dev/ttyUSB0"
28+
BAUD = 115200
29+
SAMPLE_US = 288.0
30+
31+
32+
def parse_line(line):
33+
parts = [p.strip() for p in line.split(",")]
34+
if len(parts) < 2:
35+
return None
36+
if parts[0] not in {"dump", "meas"}:
37+
return None
38+
try:
39+
sensor_id = int(parts[1])
40+
except ValueError:
41+
return None
42+
if sensor_id not in (0, 1):
43+
return None
44+
if parts[0] == "dump":
45+
try:
46+
return "dump", sensor_id, [float(v) for v in parts[2:]]
47+
except ValueError:
48+
return None
49+
if len(parts) < 5:
50+
return None
51+
try:
52+
return "meas", sensor_id, float(parts[2]), float(parts[4])
53+
except ValueError:
54+
return None
55+
56+
57+
def open_serial():
58+
return serial.Serial(
59+
port=PORT,
60+
baudrate=BAUD,
61+
bytesize=serial.EIGHTBITS,
62+
parity=serial.PARITY_NONE,
63+
stopbits=serial.STOPBITS_ONE,
64+
timeout=0.0,
65+
xonxoff=False,
66+
rtscts=False,
67+
dsrdtr=False,
68+
)
69+
70+
71+
def update_plot(ax, sensor_id, state):
72+
dump_values = state["dump"][sensor_id]
73+
meas = state["meas"][sensor_id]
74+
ax.clear()
75+
if dump_values:
76+
x = np.arange(len(dump_values), dtype=float) * SAMPLE_US
77+
ax.plot(x, dump_values, label="dump")
78+
if meas:
79+
ax.plot([meas[0]], [meas[1]], "ro", label="meas")
80+
81+
# Plot thresholds
82+
# These thresholds are defined in the firmware, so copy them from there!
83+
th_t = np.array([1000, 1400, 2400, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000, 8000])
84+
th_l = np.array([240, 50, 30, 30, 30, 25, 25, 20, 20, 20, 20, 20])
85+
last_t = 0
86+
last_l = th_l[0]
87+
for i in range(12):
88+
ax.plot([last_t, last_t + th_t[i]], [last_l, th_l[i]], "k-", label="threshold" if i == 0 else "")
89+
last_t += th_t[i]
90+
last_l = th_l[i]
91+
92+
# ax.set_xlim(0, 300)
93+
ax.set_ylim(0, 256)
94+
ax.set_title(f"Sensor {sensor_id}")
95+
ax.set_xlabel("Time (us)")
96+
ax.set_ylabel("Amplitude")
97+
ax.grid(True)
98+
ax.legend(loc="upper right")
99+
100+
101+
def main():
102+
plt.ion()
103+
fig, axes = plt.subplots(2, 1, figsize=(10, 8), constrained_layout=True)
104+
105+
state = {
106+
"dump": {0: None, 1: None},
107+
"meas": {0: None, 1: None},
108+
}
109+
110+
ser = open_serial()
111+
print(f"Connected to {PORT} @ {BAUD}")
112+
rx_buffer = bytearray()
113+
114+
while plt.fignum_exists(fig.number):
115+
changed = False
116+
117+
bytes_waiting = ser.in_waiting
118+
if bytes_waiting > 0:
119+
rx_buffer.extend(ser.read(bytes_waiting))
120+
121+
while True:
122+
newline_index = rx_buffer.find(b"\n")
123+
if newline_index < 0:
124+
break
125+
raw_line = bytes(rx_buffer[:newline_index])
126+
del rx_buffer[: newline_index + 1]
127+
line = raw_line.decode("ascii", errors="ignore").strip()
128+
if not line:
129+
continue
130+
parsed = parse_line(line)
131+
if parsed is not None:
132+
if parsed[0] == "dump":
133+
state["dump"][parsed[1]] = parsed[2]
134+
changed = True
135+
else:
136+
state["meas"][parsed[1]] = (parsed[2], parsed[3])
137+
changed = True
138+
139+
if changed:
140+
for sensor_id, ax in enumerate(axes):
141+
update_plot(ax, sensor_id, state)
142+
fig.canvas.draw_idle()
143+
144+
plt.pause(0.001)
145+
146+
if ser is not None:
147+
try:
148+
ser.close()
149+
except Exception:
150+
pass
151+
152+
153+
if __name__ == "__main__":
154+
main()

0 commit comments

Comments
 (0)