Skip to content

Commit 9d2a823

Browse files
committed
drv/bluetooth: Fix broadcasting and observing at the same time.
All three Bluetooth drivers scanned with the window equal to the interval, 30ms of every 30ms, so the radio was never free and a hub that broadcasts while observing could hardly transmit. Measurements in https://github.com/orgs/pybricks/discussions/2822 show a City Hub dropping from 28 packets/s to under 4 when it does both at once. Halving the window is not enough on its own: a 20ms interval with a 10ms window only reached 5.7 packets/s. Duty cycle does not set throughput by itself, because starting and ending a scan costs radio turnaround and scheduler time, and a 20ms period pays that fifty times a second. Use a 70ms interval with a 35ms window, which measured 12.3 to 13.0 packets/s. 70ms is not a multiple of the 25 to 30ms advertising interval of the faster hubs nor of the Move Hub's 100ms, so reception does not settle into a pattern of repeatedly missing a peer. Broadcasting on the BTstack hubs goes from every 100ms to every 30ms. 100ms was the Bluetooth 4.x minimum for non-connectable advertising, but the CC2564C is 5.1, where the minimum is 20ms, so the old value only limited how quickly other hubs could hear it. The Move Hub keeps 100ms because the BlueNRG-MS is a 4.1 controller and cannot go faster; it is the most limited hub, so it is allowed to be the slow one rather than holding the others back. City Hub and Technic Hub need one more setting. TI documents TGAP_CONN_SCAN_INT and _WIND as the scan parameters for the Link Layer Initiating state, in other words for connecting to a device, so we set only the general discovery pair. But GAP_DeviceDiscoveryRequest() uses the connection pair in place of the discovery pair whenever a connection exists, which is ordinary observing and not initiating at all. With a computer connected the chip therefore used TI's defaults of a 150ms window every 300ms. That is also a 50% duty cycle, so it looked harmless, and transmit throughput even appeared better when connected. Timestamping every advertisement received shows why it was not harmless. A City Hub observing a Technic Hub that only broadcasts received 4.9 packets/s while connected against 16.5 standalone, and the gaps between updates were either under 40ms or over 150ms with nothing in between: one short burst of reception every 300ms. The window never ran to completion either, because a connection event cuts the scan short and the remainder is discarded rather than resumed, leaving about 48ms of the 150ms. Transmit throughput looked good for the same reason the hub could barely hear anything. Worse, 300ms is a whole number of connection intervals for every interval a host is likely to choose, so the offset between a scan window and the next connection event never drifts and the same slice is lost every time. Apple hosts use 15ms, which would cap the effective window at a 5% duty cycle. Setting the pair to the same 70ms and 35ms brings reception while connected to 11.5 packets/s, and the typical wait for an update from 192ms down to 71ms against 64ms standalone. The worst case is unchanged at about 300ms, now an occasional stall rather than the normal cycle. A connected hub broadcasting and observing at once still transmits about 19 packets/s, so none of this was paid for out of the first fix.
1 parent 11d0901 commit 9d2a823

4 files changed

Lines changed: 31 additions & 7 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@
99
- On the Move Hub, `bytes()` and `bytes.find()` now truncate out-of-range
1010
values instead of raising `ValueError`.
1111

12+
### Fixed
13+
- Fixed slow broadcasting while observing at the same time ([support#2822]).
14+
- Fixed slow observing while connected to a computer or app ([support#2822]).
15+
16+
[support#2822]: https://github.com/orgs/pybricks/discussions/2822
17+
1218
[Unreleased]: https://github.com/pybricks/pybricks-micropython/compare/v4.1.0b3...HEAD
1319

1420
### Fixed

‎lib/pbio/drv/bluetooth/bluetooth_btstack.c‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1156,7 +1156,8 @@ pbio_error_t pbdrv_bluetooth_start_broadcasting_func(pbio_os_state_t *state, voi
11561156
}
11571157

11581158
bd_addr_t null_addr = { };
1159-
gap_advertisements_set_params(0xA0, 0xA0, PBIO_BLUETOOTH_AD_TYPE_ADV_NONCONN_IND, 0, null_addr, 0x7, 0);
1159+
// Advertise every 30ms so that other hubs receive broadcasts quickly.
1160+
gap_advertisements_set_params(0x30, 0x30, PBIO_BLUETOOTH_AD_TYPE_ADV_NONCONN_IND, 0, null_addr, 0x7, 0);
11601161
recorded_events.advertise_enable_complete = false;
11611162
gap_advertisements_enable(true);
11621163

@@ -1175,7 +1176,10 @@ pbio_error_t pbdrv_bluetooth_start_observing_func(pbio_os_state_t *state, void *
11751176
PBIO_OS_ASYNC_BEGIN(state);
11761177

11771178
if (!pbdrv_bluetooth_is_observing) {
1178-
gap_set_scan_params(0, 0x30, 0x30, 0);
1179+
// 70ms interval, 35ms window. The 50% duty cycle leaves radio time
1180+
// for broadcasting while observing, and the longer interval spends
1181+
// less of the radio on starting and ending scans.
1182+
gap_set_scan_params(0, 0x70, 0x38, 0);
11791183
gap_start_scan();
11801184
pbdrv_bluetooth_is_observing = true;
11811185
// REVISIT: use callback to await operation

‎lib/pbio/drv/bluetooth/bluetooth_stm32_bluenrg.c‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,8 @@ pbio_error_t pbdrv_bluetooth_start_broadcasting_func(pbio_os_state_t *state, voi
587587

588588
if (pbdrv_bluetooth_advertising_state != PBDRV_BLUETOOTH_ADVERTISING_STATE_BROADCASTING) {
589589
PBIO_OS_AWAIT_WHILE(state, write_xfer_size);
590+
// This chip cannot broadcast faster than every 100ms, so other hubs
591+
// receive from it more slowly than they do from each other.
590592
aci_gap_set_non_connectable_begin(ADV_NONCONN_IND, STATIC_RANDOM_ADDR);
591593
PBIO_OS_AWAIT_UNTIL(state, hci_command_complete);
592594
status = aci_gap_set_non_connectable_end();
@@ -642,7 +644,10 @@ pbio_error_t pbdrv_bluetooth_start_observing_func(pbio_os_state_t *state, void *
642644
// the observer role which would use more RAM in the Bluetooth chip
643645

644646
PBIO_OS_AWAIT_WHILE(state, write_xfer_size);
645-
aci_gap_start_general_conn_establish_proc_begin(PASSIVE_SCAN, 0x30, 0x30, STATIC_RANDOM_ADDR, 0);
647+
// 70ms interval, 35ms window. The 50% duty cycle leaves radio time for
648+
// broadcasting while observing, and the longer interval spends less of the
649+
// radio on starting and ending scans.
650+
aci_gap_start_general_conn_establish_proc_begin(PASSIVE_SCAN, 0x70, 0x38, STATIC_RANDOM_ADDR, 0);
646651
PBIO_OS_AWAIT_UNTIL(state, hci_command_status);
647652
status = aci_gap_start_general_conn_establish_proc_end();
648653
if (status == BLE_STATUS_SUCCESS) {

‎lib/pbio/drv/bluetooth/bluetooth_stm32_cc2640.c‎

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1562,10 +1562,19 @@ static const struct {
15621562
{TGAP_GEN_DISC_ADV_INT_MAX, 40},
15631563
{TGAP_CONN_ADV_INT_MIN, 40},
15641564
{TGAP_CONN_ADV_INT_MAX, 40},
1565-
// scan interval general discovery: 48 * 0.625ms = 30ms
1566-
{TGAP_GEN_DISC_SCAN_INT, 48},
1567-
// scan window general discovery: 48 * 0.625ms = 30ms
1568-
{TGAP_GEN_DISC_SCAN_WIND, 48},
1565+
// Scan interval 112 * 0.625ms = 70ms. Starting and ending a scan costs
1566+
// radio and scheduler time, so a longer interval leaves more of the radio
1567+
// for broadcasting. 70ms is not a multiple of any hub's advertising
1568+
// interval, so reception does not settle into a pattern of missing them.
1569+
{TGAP_GEN_DISC_SCAN_INT, 112},
1570+
// Scan window 56 * 0.625ms = 35ms. The 50% duty cycle leaves radio time
1571+
// for broadcasting while observing.
1572+
{TGAP_GEN_DISC_SCAN_WIND, 56},
1573+
// The Bluetooth chip quietly uses these instead of the two above whenever
1574+
// there is a connection, so they have to match or observing collapses
1575+
// while connected to Pybricks Code.
1576+
{TGAP_CONN_SCAN_INT, 112},
1577+
{TGAP_CONN_SCAN_WIND, 56},
15691578
{TGAP_CONN_EST_INT_MIN, 40},
15701579
{TGAP_CONN_EST_INT_MAX, 40},
15711580
// scan interval connection established: 48 * 0.625ms = 30ms

0 commit comments

Comments
 (0)