Open iKK001 opened 1 year ago
As have spent a lot of time on the ArduinoBLE code in a number of projects, I decided to take a look. I could reproduce ' freeze', and maybe it is the same. There are multiple issues to consider.
Make sure you use the latest V1.3.2. Especially because of the issues https://github.com/arduino-libraries/ArduinoBLE/issues/281 and https://github.com/arduino-libraries/ArduinoBLE/pull/264. This is especially important is there are multiple BLE peripheral devices available.
In your code you VERY often trigger the BLE.scanForUuid() unnecessarily. It could cause the HCI layer to go out of sync or "bananas'. Seems that was one of the reasons for the freeze or the timing of it on my Sparkfun Apollo3 / ATP board. A new scan will cause the to first stop scanning, set the scan parameters and start scanning again. Not necessary and can be handled with the changes in the code below. A scan request does not timeout.
I changed the code so I could run a test against a peripheral. During my tests the peripheral shutdown while it was trying to perform the first steps of explorePeripheral(). Hence I reset the 'DoScan' after each call to explorePeripheral(). No matter whether it returned true or false. That return value is not used anyway.
The peripheral.disconnect() at the end of explorePeripheral() is unnecessarily(), assuming that this was triggered from the peripheral already. It is not able to cause a disconnect as it is disconnected already. It was not a reason for freeze.. just not necessary.
After the code changes it kept running for 3 hours.. no problems... it would fail after15 minutes before..
Maybe it helps a bit.
regards Paul
#include <ArduinoBLE.h>
char* PERIPHERAL_LOCAL_NAME = "SOME PERIPHERAL NAME";
#define BLE_UUID_SENSOR_DATA_SERVICE "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
#define BLE_UUID_MULTI_SENSOR_DATA "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"
#define BAUDRATE 115200
#define UPDATE_INTERVALL 10
#define BLE_POLL_INTERVALL 5
uint64_t previousMillis = 0;
bool DoScan = true; // ADDED
void setup() {
Serial.begin(BAUDRATE);
delay(2000);
previousMillis = millis();
BLE.begin();
// BLE.scanForUuid(BLE_UUID_SENSOR_DATA_SERVICE); // REMOVE
}
void loop() {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis > UPDATE_INTERVALL) {
previousMillis = currentMillis;
if (DoScan) { // CHANGED DO NOT HAMMER HCI LAYER
BLE.scanForUuid(BLE_UUID_SENSOR_DATA_SERVICE);
DoScan = false;
}
BLEDevice peripheral = BLE.available();
if (peripheral) {
if (peripheral.localName() != PERIPHERAL_LOCAL_NAME) {
return;
}
BLE.stopScan();
explorePeripheral(peripheral);
DoScan = true; // ADDED
}
}
}
bool explorePeripheral(BLEDevice peripheral) {
if (!peripheral.connect()) {
return false;
}
if (!peripheral.discoverAttributes()) {
peripheral.disconnect();
return false;
}
BLECharacteristic multiSensorDataCharacteristic = peripheral.characteristic(BLE_UUID_MULTI_SENSOR_DATA);
if (!multiSensorDataCharacteristic) {
peripheral.disconnect();
return false;
}
if (!multiSensorDataCharacteristic.canSubscribe()) {
peripheral.disconnect();
return false;
}
if (!multiSensorDataCharacteristic.subscribe()) {
peripheral.disconnect();
return false;
}
while (peripheral.connected()) {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis > BLE_POLL_INTERVALL) {
previousMillis = currentMillis;
BLE.poll();
if (multiSensorDataCharacteristic.valueUpdated()) {
multiSensorDataCharacteristic.readValue(...);
}
// if (peripheral.rssi() != 0) {
// digitalWrite(RSSI_OK_LED_PIN, HIGH);
// } else {
// digitalWrite(RSSI_OK_LED_PIN, LOW); // red alarm LED ON
// }
}
}
//peripheral.disconnect(); // ASSUME YOUR DISCONNECT IS TRIGGERED FROM REMOTE
return true;
}```
@paulvha: Thank you very much ! I'll definitively give it a try. I'll let you know what happens.
Oh no, it ran 2.5h now - so much better. But then again the freeze !!!!!
After a couple more tests, I have to recognise that the freeze is happening randomly between 5min and 3 hours. But it still happens every time!
I ran the following code - including your DoScan
boolean.
Shall I go back to the UPDATE_INTERVAL loop rather ??
Does it matter if the 24h receiver is a Central (and the Sender is a Peripheral accordingly) ? Or do the roles rather be the opposite ? Does it matter ?
What could be the reason of the new freeze now ??
Again, the Central Receiver is supposed to run 24h (USB-powered).
The Peripheral Sender is powered-off 99% and will only power-ON a couple of times during the day to send a few data and then power-off completely again.
unsigned long previousMillis = 0;
bool DoScan = true;
void setup() {
Serial.begin(BAUDRATE);
delay(2000);
previousMillis = millis();
BLE.begin();
}
void loop() {
BLEDevice peripheral;
do {
peripheral = BLEDevice();
// do not scan all the time in the loop to protect HCI-layer
if (DoScan) {
BLE.scanForUuid(BLE_UUID_SENSOR_DATA_SERVICE);
DoScan = false;
}
peripheral = BLE.available();
} while (!peripheral);
if (peripheral) {
if (peripheral.localName() != PERIPHERAL_LOCAL_NAME) {
return;
}
BLE.stopScan();
explorePeripheral(peripheral);
DoScan = true;
}
}
bool explorePeripheral(BLEDevice peripheral) {
// ...
// unchanged to initial code example
// ...
}
My Peripheral Sender Code looks like this:
#include <ArduinoBLE.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BNO055.h>
char* PERIPHERAL_LOCAL_NAME = "SOME PERIPHERAL NAME";
#define BLE_UUID_SENSOR_DATA_SERVICE "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
#define BLE_UUID_MULTI_SENSOR_DATA "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"
#define BAUDRATE 115200
#define UPDATE_INTERVALL 50
#define BATTERY_CHARGING_TYPE_PIN P0_13 // internal
#define SEND_DATA_BUTTON_PIN D10
BLEService sensorDataService(BLE_UUID_SENSOR_DATA_SERVICE);
BLECharacteristic multiSensorDataCharacteristic(BLE_UUID_MULTI_SENSOR_DATA, BLERead | BLENotify, sizeof multiSensorData.bytes);
bool setupBleMode() {
if (!BLE.begin()) {
return false;
}
// set advertised local name and service UUID:
BLE.setDeviceName(PERIPHERAL_LOCAL_NAME); // make it the same...
BLE.setLocalName(PERIPHERAL_LOCAL_NAME);
BLE.setAdvertisedService(sensorDataService);
// BLE add characteristics
sensorDataService.addCharacteristic(multiSensorDataCharacteristic);
// add service
BLE.addService(sensorDataService);
// set the initial value for the characeristic:
multiSensorDataCharacteristic.writeValue(multiSensorData.bytes, sizeof multiSensorData.bytes);
// start advertising
BLE.advertise();
return true;
}
void setup() {
Serial.begin(BAUDRATE);
delay(2000);
setupBleMode();
}
void loop() {
static long previousMillis = 0;
// listen for BLE peripherals to connect:
BLEDevice central = BLE.central();
if (central) {
Serial.print(F("Connected to central: "));
Serial.println(central.address());
while (central.connected()) {
unsigned long updateMillis = millis();
if (updateMillis - previousMillis > UPDATE_INTERVALL) {
previousMillis = updateMillis;
if (central.rssi() != 0) {
multiSensorDataCharacteristic.writeValue(..., sizeof multiSensorData.bytes);
}
}
}
Serial.print(F("Disconnected from central: "));
Serial.println(central.address());
}
}
good progress but not there yet indeed I will try to simulate today, but quick feedback. Central set the line BLEDevice peripheral; as global variable.
remove peripheral = BLEDevice(); This will create a new device, taking memory every time. It is not needed and maybe you run out of memory and crash as a result. A BLE-Device is created when a scan report of a not-discovered-yet device is received. You then obtain that BLE-Device by calling "BLE.available()".
Before the line peripheral = BLE.available(); add BLE.poll();. This will make sure data is flowing from the HCI layer.
Peripheral How do you power-off the peripheral? Is there a proper disconnect send to the Central ? As you do the test, do you have the peripheral on all the time or will the central stop without any connection ?
regards, Paul
Thanks, I will test with your proposed changes...
And to answer your questions:
How do you power-off the peripheral? Is there a proper disconnect send to the Central ?
--> I just do power-off. There is no disconnect sent from the Peripheral-Sender to the Central-Receiver since there is no time for it (i.e. the Peripheral-Sender is simply taken off from its power-supply).
As you do the test, do you have the peripheral on all the time or will the central stop without any connection ?
--> The Peripheral-Sender is only ON for a short time (i.e. it is a handheld with battery and has a power-switch that the user applies). It connects to the Central-Receiver, sends a couple of sensor-data and then the user powers-OFF the Peripheral-Sender again. The Sender is ON only a couple of times a day, very shortly.
--> The Central-Receiver is ON 24h. It is plugged to a Computer-USB and is supposed to connect to the Peripheral-Sender if the Sender is ON. It listens 24h for Sensor-availablilty and upon recognition, sends the incoming sensor-data to the Serial-port of the Computer-USB. The Central-Receiver shall not freeze if the Peripheral-Sender is OFF-powered at any state. It should rather stay alive and connect to the Peripheral-Sender as soon as it is available.
Short update.. none of the central advice above helped. :-( Doing my test (Artemis ATP / Apollo3)
After 5 minutes with NO advertise report received during the scanning, the central is not picking up any advertise reports anymore. If I start the peripheral at that moment, the central does not see that advertise report either I can connect with the peripheral with nrf-connect. it is for sure something on the central side.
I have included many debug messages and changed the code that when I press
more to follow
thank you for your report. This is exactly what I observe also.
The Central-Receiver does not recognise the Peripheral-Sender after the Peripheral-Sender has been OFF a certain time and comes back ON. It is as if the Central-Receiver is no longer doing the available()
check anymore or can not connect
properly anymore. The Receiver does not work anymore after the Sender has been off and comes back on. It can only do so shortly after being powered-on itself.
Have not found the solution yet... but some update that can help I have 2 boards: Sparkfun nrf52480 micromod (MM) and a Sparkfun Artemis / Apollo3 ATP.
In the first tests the MM board was the peripheral and the ATP the central. The ATP central is not reacting to an advertisement report if 5 minutes have passed since the last advertisement report. Even when a 'keep-alive' to read the HCI-chip version is done every 4 minutes. That HCI-chip version request is respond to correctly. Also after the 5 minutes and the MM is now advertising, but the ATP not reacting. There is NO interrupt is received from the Apollo3 chip. So this is NOT an error of ArduinoBLE. Somehow something is wrong with the Apollo3 HCI. Do not know what yet.
Now if I do the opposite the MM is now the central and the ATP the peripheral. Even after 15 min the MM is still responding to an advertisement report.
There is potentially an important change to be made to the ArduinoBLE code. Whenever ArduinpBLE receives a disconnect complete from the HCI chip, it will clear the necessary tables, BUT it will also start advertising EVEN IF YOU ARE RUNNING AS CENTRAL. The ATP HCI chip does not like that and fails to start scanning. My MM does not seem to mind and scanning is started. It is an old problem I discovered before and I corrected my local version some time ago.
But just to be sure this is not impacting you, as you might have a different SoftDevice (BLE) in the nrf52480, make the following change: In the ArduinoBLE/src/utility folder there is a file HCI.cpp. Around line 828 in the file comment out the line 'HCI.leSetAdvertiseEnable(0x01);'
There are 2 other options to consider :
From the loop in the sketch, every 5 min perform a BLE.scan().
Switch roles where the BN0055 is connected sensor becomes the central. Add a write-characteristic in the peripheral and the peripheral is always on. Once the central connects, the peripheral will send a "hallo" over notify and then the central will write the data on the extra write-characteristic and disconnect. It is a 'little' against the BLE architecture thoughts.. but I will not tell anybody.
regards, Paul
Dear Paul, thank you very much for your investigation and workaround suggestions.
It seems that certain HW-boards indeed stop reacting to advertisement reports after ~5mins !
For example, I also tried the board Xiao BLE nRF52840
(from Seeed Studio), one being Peripheral-Sender and a second device being Central-Receiver - and same thing: Central-Receiver stops reacting to the Sender Power-ON after a certain time running. Same false behaviour, it stops recognising.
What I did not quite understand from your response:
There is potentially an important change to be made to the ArduinoBLE code. (HCI.leSetAdvertiseEnable(0x01);
:
Do I have to do that Code-change in ANY case ? Or only if I keep the non-working roles (Peripheral-Sender & Central-Receiver) ? Or do I have to make that Code-change also for the switched roles (i.e. Peripheral-Receiver & Central-Sender) ??
In the meantime, I will try to switch roles (Peripheral vs. Central) and check my setups.
Hope we advance some more with the open question why some boards do show the Central-Receiver-freeze.
it is still weird. I did a lot of checking on Apollo3 settings. I displayed many of the registers and none of them are different between being responsive or not to an advertising packet. I often see the advertisement packet passed to ArduinoBLE, but never a scan response (which ArduinoBLE expects). Maybe a SCAN-REQ is not send, maybe an issue with the clock.. don't know yet. More brainstorming is needed.
With respect to your question. Whenever there is a disconnect complete (either running as central or peripheral) ArduinoBLE will start advertising again with the command '(HCI.leSetAdvertiseEnable(0x01);' It should actually check that advertize data was set before and only then restart advertising. My Apollo3 processor did not like to be started without advertize data and it and got stuck. Any subsequent request for start scanning failed and then of course never reacts on any advertize packet. When I checked that on the nrf52480, it did not mind and subsequent request to start scanning was handled correctly. BUT there are many versions of BLE (called Softdevice on nrf) and maybe.. maybe your version does not like it either. hence I propose to make the change.
regards, Paul
Dear Paul, one more finding:
This board works (testing hours = 6): The Arduino Nano 33 BLE board works out of the box ! The Central-Receiver stays alive and reconnects to any Peripheral-Sender powering-ON, without any issue even after 6 hours.
I'm still using the newest ArduinoBLE v1.3.2 (and it works even without the HCI.cpp fix you suggested, i.e. 'HCI.leSetAdvertiseEnable(0x01);` - but it would not hurt either as you explained).
To recall, the non-working board that I used before was the Seeed Studio Xiao nRF52840 BLE. That one does not work and Central-Receiver freezes as we know. In their Forum I placed the same problem-description. However, not very much useful contribution to the issue yet.
For information, I did not yet have time to test the HCI.cpp change on the Seeed Xiao nRF52840 yet. But I'll let you know as soon as I do).
In summary, the Central-Receiver "freeze" definitively seems to be a HW-related-cause.
The big question that remains is if the ArduinoBLE library can do anything about it ?
We might have to admit that there are hardwares out there that are not fully compatible with the ArduionoBLE library.
What do you think ? Any more findings on your side ?
Bad news: after 12 hours the Arduino Nano 33 BLE board also stopped working. It froze as well !
Moreover, the Seeed Studio Xiao nRF52840 BLE board does not work with the SW-change in HCI.cpp (i.e. commenting out HCI.leSetAdvertiseEnable(0x01); does not help).
So I am back to zero.
The fact that the second board ran longer, makes me think it is a memory problem. What is memory-related in the ArduinoBLE library during Central-Receive while Peripheral-Sender is off ???
Which board worked for you ? And how long ?
I have tried a different Apollo3 board and that failed as well. later today I will set the nrf52480 that had not failed after a couple of hours for longer test. Who knows ?
I have also raised this at Ambiq. Maybe a known issue .. no answer yet.
The issue for me is that the Apollo3 HCI-chip is unresponsive. It does not raise an interrupt and thus ArduinoBLE is not getting the data to handle. If I then just do a BLE.scan(), it all works again. If it was memory related, I would not expect it to work. Also a keep-alive to read the local version every 1 minute, keeps working. I will try today to create a scanner based on Cordio (the BLE stack in MBED).
ok - you have fantastic knowledge. Thanks for hanging in there...
Is the Apollo3 HCI-chip on all the boards ? (i.e. Arduino Nano 33 BLE / Seeed Xioa nRF52840 BLE / Sparkfun nrf52480 micromod / Sparkfun Artemis) ??
no the Apollo3 is only on the Sparkfun
The nrf52480 is in the XIOa, micro mod and the Arduino nano 33 BLE.
bad news : I have just completed the test on the Apollo3 and it failed also. I have used a completely different BLE stack (Exactle which was later integrated in Cordio). NONE of the ArduinoBLE code is used. after 5 min the same result More work to be done, but I start thinking that maybe the settinf "filter duplicates " during start scan has something to do with this.
Thank you, Paul, for your valuable investigations. Please let me know if I can test some more on my boards. (I'm afraid that I am not as deeply into the topic to contribute with the settinf issue).
I have changed the code so that the HCI chip scan does not filter on duplicates. Now ArduinoBLE get's 'hammered' with each and every advertise report it gets (and there are MANY !!) and they are all passed on to AruidnoBLE. The good news...: ArduinoBLE with the Apollo3 HCI-chip is still responsive 4 hours later (where it bailed out after 5 minutes before). More testing to be done.. but this looks promising to at least understand the root cause and maybe find a solution.
To test: in src/utility/GAP.cpp. around line 96 in the function scan() remove the exclamation mark and thus change
if (HCI.leSetScanEnable(true, !withDuplicates) != 0)
to
if (HCI.leSetScanEnable(true, withDuplicates) != 0)
regards, Paul
I tried to switch roles (Peripheral vs. Central) - since you had described it as a potential temporary workaround.
The two Seeed Xiao nRF52840 devices connect and it seems an improvement - however, the Central-Sender cannot write values to the Peripheral-Receiver.
It has most likely nothing to do with our original problem. Except that I need to switch roles to investigate that this could be a temporary workaround.
Below are the two switched-roles-Codes.
Thank you for having a quick look.
The problem is that the Central-Sender fails to send values. i.e. where it says:
bool returnValue = multiSensorDataCharacteristic.writeValue((byte)0x01);
Serial.println(returnValue);
Thanks for any hint why this returnValue is not returning 1
for success but always 0
for failure.
(I did the same thing I did before with the Peripheral-Sender - but this time as a Central-Sender)...
First, the code for the Central-Sender:
/*
* ---------------------------------------
* BLE Central (Sender)
* ---------------------------------------
*/
#include <ArduinoBLE.h>
char* PERIPHERAL_LOCAL_NAME = "SOME PERIPHERAL NAME";
#define BLE_UUID_SENSOR_DATA_SERVICE "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
#define BLE_UUID_MULTI_SENSOR_DATA "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"
#define BAUDRATE 115200
#define UPDATE_INTERVALL 50
// #define RSSI_OK_LED_PIN LED_BUILTIN // (= LEDR)
#define LED_R_PIN LEDR // (= LED_BUILTIN)
#define LED_G_PIN LEDG
#define LED_B_PIN LEDB
#define BATTERY_CHARGING_TYPE_PIN P0_13 // internal
#define SEND_DATA_BUTTON_PIN D10
unsigned long previousMillis = 0;
unsigned long updateMillis = 0;
BLEDevice peripheral;
bool doScan = true;
void setup() {
Serial.begin(BAUDRATE);
delay(2000);
// pinMode(RSSI_OK_LED_PIN, OUTPUT); // equivalent to pinMode(LED_R_PIN, OUTPUT);
pinMode(LED_R_PIN, OUTPUT);
pinMode(LED_G_PIN, OUTPUT);
pinMode(LED_B_PIN, OUTPUT);
// turn all RGB-leds off
// digitalWrite(RSSI_OK_LED_PIN, HIGH);
digitalWrite(LED_R_PIN, HIGH);
digitalWrite(LED_G_PIN, HIGH);
digitalWrite(LED_B_PIN, HIGH);
previousMillis = millis();
updateMillis = millis();
digitalWrite(BATTERY_CHARGING_TYPE_PIN, LOW); // HIGH = low battery-charing / LOW = high battery-charging current
}
void loop() {
do {
peripheral = BLEDevice();
// do not scan all the time in the loop to protect HCI-layer
if (doScan) {
BLE.begin();
BLE.scanForUuid(BLE_UUID_SENSOR_DATA_SERVICE);
doScan = false;
}
BLE.poll();
peripheral = BLE.available();
updateMillis = millis();
if (updateMillis - previousMillis > UPDATE_INTERVALL * 10) {
previousMillis = updateMillis;
doScan = true;
}
} while (!peripheral);
if (peripheral) {
if (peripheral.localName() != PERIPHERAL_LOCAL_NAME) {
return;
}
BLE.stopScan();
communicateToPeripheral(peripheral);
doScan = true;
}
}
void communicateToPeripheral(BLEDevice peripheral) {
if (!peripheral.connect()) {
return;
}
// Serial.print(F("Connected to peripheral: "));
// Serial.print(peripheral.localName()); Serial.print(" ("); Serial.print(peripheral.address()); Serial.println(")");
if (!peripheral.discoverAttributes()) {
peripheral.disconnect();
return;
}
// Serial.println("BLE attributes discovered");
BLECharacteristic multiSensorDataCharacteristic = peripheral.characteristic(BLE_UUID_MULTI_SENSOR_DATA);
if (!multiSensorDataCharacteristic) {
peripheral.disconnect();
return;
}
// Serial.println("BLE characteristic found");
if (!multiSensorDataCharacteristic.canSubscribe()) {
peripheral.disconnect();
return;
}
// Serial.println("BLE characteristic can subscribe");
if (!multiSensorDataCharacteristic.subscribe()) {
peripheral.disconnect();
return;
}
// Serial.println("BLE characteristic subscribed");
// set the initial value for the characeristic:
// multiSensorDataCharacteristic.writeValue(multiSensorData.bytes, sizeof multiSensorData.bytes);
while (peripheral.connected()) {
updateMillis = millis();
if (updateMillis - previousMillis > UPDATE_INTERVALL) {
previousMillis = updateMillis;
bool returnValue = multiSensorDataCharacteristic.writeValue((byte)0x01);
Serial.println(returnValue);
}
}
peripheral.disconnect();
// Serial.print(F("Disconnected from peripheral: "));
// Serial.print(peripheral.localName()); Serial.print(" ("); Serial.print(peripheral.address()); Serial.println(")");
return;
}
And then the code for the Peripheral-Receiver:
/*
* -------------------------------------
* BLE Peripheral (Receiver)
* -------------------------------------
*/
#include <ArduinoBLE.h>
char* PERIPHERAL_LOCAL_NAME = "SOME PERIPHERAL NAME";
#define BLE_UUID_SENSOR_DATA_SERVICE "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
#define BLE_UUID_MULTI_SENSOR_DATA "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"
#define BAUDRATE 115200
#define BLE_POLL_INTERVALL 5
#define BATTERY_CHARGING_TYPE_PIN P0_13 // internal
// #define RSSI_OK_LED_PIN LED_BUILTIN // (= LEDR)
#define LED_R_PIN LEDR // (= LED_BUILTIN)
#define LED_G_PIN LEDG
#define LED_B_PIN LEDB
#define SEND_DATA_BUTTON_PIN D10
unsigned long previousMillis = 0;
unsigned long currentMillis = 0;
bool doScan = true;
byte myValue = 0;
BLEDevice central;
BLEService sensorDataService(BLE_UUID_SENSOR_DATA_SERVICE);
BLECharacteristic multiSensorDataCharacteristic(BLE_UUID_MULTI_SENSOR_DATA, BLERead | BLENotify, sizeof myValue);
bool setupBleMode() {
if (!BLE.begin()) {
return false;
}
// set advertised local name and service UUID:
BLE.setDeviceName(PERIPHERAL_LOCAL_NAME); // make it the same...
BLE.setLocalName(PERIPHERAL_LOCAL_NAME);
BLE.setAdvertisedService(sensorDataService);
// BLE add characteristics
sensorDataService.addCharacteristic(multiSensorDataCharacteristic);
// add service
BLE.addService(sensorDataService);
// start advertising
BLE.advertise();
return true;
}
void setup() {
Serial.begin(BAUDRATE);
delay(2000);
// pinMode(RSSI_OK_LED_PIN, OUTPUT); // equivalent to pinMode(LED_R_PIN, OUTPUT);
pinMode(LED_R_PIN, OUTPUT);
pinMode(LED_G_PIN, OUTPUT);
pinMode(LED_B_PIN, OUTPUT);
// turn all RGB-leds off
// digitalWrite(RSSI_OK_LED_PIN, HIGH);
digitalWrite(LED_R_PIN, HIGH);
digitalWrite(LED_G_PIN, HIGH);
digitalWrite(LED_B_PIN, HIGH);
pinMode(BATTERY_CHARGING_TYPE_PIN, OUTPUT); // Battery charging
pinMode(SEND_DATA_BUTTON_PIN, INPUT_PULLUP);
setupBleMode();
previousMillis = millis();
currentMillis = millis();
}
void loop() {
digitalWrite(LED_R_PIN, LOW); // turn red LED on
digitalWrite(LED_G_PIN, HIGH); // turn green LED off
digitalWrite(LED_B_PIN, HIGH); // turn blue LED off
digitalWrite(BATTERY_CHARGING_TYPE_PIN, LOW); // HIGH = low battery-charing / LOW = high battery-charging current
// this is needed for some reason - otherwise device never connects again to the central (when central was off for some time)
if (doScan) {
doScan = false;
setupBleMode();
}
// listen for BLE peripherals to connect:
central = BLE.central();
if (central) {
// Serial.print(F("Connected to central: "));
// Serial.println(central.address());
while (central.connected()) {
digitalWrite(LED_R_PIN, HIGH); // turn red LED off
digitalWrite(LED_G_PIN, LOW); // turn green LED on
digitalWrite(LED_B_PIN, HIGH); // turn blue LED off
currentMillis = millis();
if (currentMillis - previousMillis > BLE_POLL_INTERVALL) {
previousMillis = currentMillis;
BLE.poll();
if (multiSensorDataCharacteristic.written()) {
multiSensorDataCharacteristic.readValue(myValue);
Serial.print("value = ");
Serial.println(myValue);
}
}
}
central.disconnect();
// Serial.print(F("Disconnected from central: "));
// Serial.println(central.address());
doScan = true;
}
}
I'll have a look at your sketches later today, but first some good news. the Apollo3 is still running and reacting for 17 hours now !!.
Instead of making the change in gap.cpp as I indicated before, in the original sketch add 'true' to scan. So change
BLE.scanForUuid(BLE_UUID_SENSOR_DATA_SERVICE);
to
BLE.scanForUuid(BLE_UUID_SENSOR_DATA_SERVICE, true);
(see https://www.arduino.cc/reference/en/libraries/arduinoble/ble.scanforuuid/)
give it try :-) regards, Paul
To your sketches. you can not write from the central to multiSensorDataCharacteristic(). Only the peripheral can do that. you need to create another charactertistic with BLERead and BLEWrite so the central can write to it. Once the peripheral receives the trigger on the new characteristic, the peripheral can write the data with notify on the multiSensorDataCharacteristic.
Oh, I did not see that you had posted 2 messages. Great news !
I will test tonight with your finding (i.e. BLE.scanForUuid(BLE_UUID_SENSOR_DATA_SERVICE, true);
) and I'll let you know how it goes.
In the meantime I have the "switched roles workaround" working (i.e. Central-Sender and Peripheral-Receiver). I must have been very tired yesterday. With your hint to create another characteristic with BLERead | BLEWrite
, it all worked! (Testing time is only 1h now - but as you found out - in this config we do not have the freeze-problem.)
For completeness reasons, here my corrected characteristic for the Peripheral-Receiver:
byte myValue = 0;
BLECharacteristic multiSensorDataCharacteristic(BLE_UUID_MULTI_SENSOR_DATA, BLERead | BLEWrite, sizeof myValue);
And for the Central-Sender I had to take out the subscription parts:
// if (!multiSensorDataCharacteristic.canSubscribe()) {
// peripheral.disconnect();
/// return;
// }
// Serial.println("BLE characteristic can subscribe");
// if (!multiSensorDataCharacteristic.subscribe()) {
// peripheral.disconnect();
// return;
// }
Afterwards, it worked !
Again, I prefer the original roles (i..e Peripheral-Sender and Central-Receiver). Reason: there might be more Peripherals eventually....
And for sure, I will try your finding right away and I'll give you feedback tomorrow evening.
Thank you Paul, for your tremendous contributions. Sincerely, Stephan
It passed more then 24 hours of scanning on the Apollo3 and it is still running. I think the root cause is that filtering in the HCI-layer for advertise packets is set by default. That said.. it is not an error, I think it is the right way if you scan for a short time only to find a peripheral available at that moment. Filtering means that IF during a scanning-session there is an advertise or scan report that was received from THAT peripheral before, it is neglected and not passed to ArduinoBLE. But for 24 hours of scanning, disable the filtering, as many HCI -in-chip-layer seem to have limited memory.
Got feedback from Ambiq. The Cordio / 'old ExactLE' stack has a variable that is set to 10. It will NOT store more scan reports and neglect all the rest until the next scan is started. ArduinoBLE is handling a differently: Make sure to call BLE.available() often enough BUT if that is NOT happening they have created a patch so the current received report is not lost.
I wonder however whether other people who had the long scan issue before could have solved it by setting 'true" to allow duplicates.
So it seems you have 2 options to solve the issue. : switch roles and /or allow duplicates :-)
regards, paul
OK! at least the workarounds start increasing in numbers ;)
I have bad news as for the Seeed Xiao nRF52840 BLE: it froze again after approx 5 minutes!
At the first few minutes, it was able to connect again (but with approx. 20sec delay). But after 5min no more connections there. The Central-Receiver froze again!
BLE.scanForUuid(BLE_UUID_SENSOR_DATA_SERVICE, true);
// HCI.leSetAdvertiseEnable(0x01);
commented outHere is my Central-Receiver loop:
void loop() {
do {
peripheral = BLEDevice();
BLE.begin();
BLE.scanForUuid(BLE_UUID_SENSOR_DATA_SERVICE, true);
BLE.poll();
peripheral = BLE.available();
} while (!peripheral);
if (peripheral) {
if (peripheral.localName() != PERIPHERAL_LOCAL_NAME) {
return;
}
BLE.stopScan();
explorePeripheral(peripheral);
}
}
It looks like I have only one workaround with the Seeed Xiao nRF52840 board: to switch roles.
I will try the same thing with the Arduino Nano 33 BLE board as well...
can you share the complete sketch. This is what I have used :
#include <ArduinoBLE.h>
char* PERIPHERAL_LOCAL_NAME = "Peripheral BLE";
#define BLE_UUID_SENSOR_DATA_SERVICE "19B10030-E8F2-537E-4F6C-D104768A1214"
#define BLE_UUID_MULTI_SENSOR_DATA "19B10032-E8F2-537E-4F6C-D104768A1214"
#define BAUDRATE 115200
#define UPDATE_INTERVALL 10
#define BLE_POLL_INTERVALL 5
long loopcount = 0 ;
BLEDevice peripheral;
uint64_t previousMillis = 0;
bool DoScan = true;
void setup() {
Serial.begin(BAUDRATE);
delay(2000);
previousMillis = millis();
BLE.begin();
while (Serial.available()){
Serial.read();
delay(100);
}
}
void loop() {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis > UPDATE_INTERVALL) {
previousMillis = currentMillis;
// do not scan all the time in the loop to protect HCI-layer
if (DoScan) {
Serial.println("set scan \n");
BLE.scanForUuid(BLE_UUID_SENSOR_DATA_SERVICE, true);
DoScan = false;
}
if(loopcount++ > 400) {
if (Serial.available()){ // if enter is pressed trigger manual scan in case it stopped,
delay(100); // empty anything pending
while (Serial.available()){
Serial.read();
delay(100);
}
DoScan = true; // scan next loop
}
loopcount=0; // reset loopcount for next check
}
BLE.poll();
peripheral = BLE.available();
if (peripheral) {
Serial.println("peripheral");
if (peripheral.localName() != PERIPHERAL_LOCAL_NAME) {
return;
}
BLE.stopScan();
explorePeripheral(peripheral);
DoScan = true;
}
}
}
Here is my complete sketch:
/*
* -----------------------------
* BLE Central (Receiver)
* -----------------------------
*
*/
#include <ArduinoBLE.h>
char* PERIPHERAL_LOCAL_NAME = "SOME PERIPHERAL NAME";
#define BLE_UUID_SENSOR_DATA_SERVICE "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
#define BLE_UUID_MULTI_SENSOR_DATA "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"
#define BAUDRATE 115200
#define UPDATE_INTERVALL 10
#define BLE_POLL_INTERVALL 5
// #define RSSI_OK_LED_PIN LED_BUILTIN // (= LEDR)
#define LED_R_PIN LEDR // (= LED_BUILTIN)
#define LED_G_PIN LEDG
#define LED_B_PIN LEDB
unsigned long previousMillis = 0;
BLEDevice peripheral;
bool doScan = true;
byte myValue = 0;
void setup() {
Serial.begin(BAUDRATE);
delay(2000);
// pinMode(RSSI_OK_LED_PIN, OUTPUT); // equivalent to pinMode(LED_R_PIN, OUTPUT);
pinMode(LED_R_PIN, OUTPUT);
pinMode(LED_G_PIN, OUTPUT);
pinMode(LED_B_PIN, OUTPUT);
// turn all RGB-leds off
// digitalWrite(RSSI_OK_LED_PIN, HIGH);
digitalWrite(LED_R_PIN, HIGH);
digitalWrite(LED_G_PIN, HIGH);
digitalWrite(LED_B_PIN, HIGH);
previousMillis = millis();
}
void loop() {
digitalWrite(LED_R_PIN, LOW); // turn red LED on
digitalWrite(LED_G_PIN, HIGH); // turn gren LED off
digitalWrite(LED_B_PIN, HIGH); // turn red LED off
do {
peripheral = BLEDevice();
// do not scan all the time in the loop to protect HCI-layer
if (doScan) {
BLE.begin();
BLE.scanForUuid(BLE_UUID_SENSOR_DATA_SERVICE, true);
doScan = false;
}
BLE.poll();
peripheral = BLE.available();
} while (!peripheral);
if (peripheral) {
if (peripheral.localName() != PERIPHERAL_LOCAL_NAME) {
return;
}
BLE.stopScan();
explorePeripheral(peripheral);
doScan = true;
}
}
bool explorePeripheral(BLEDevice peripheral) {
if (!peripheral.connect()) {
return false;
}
// Serial.print(F("Connected to peripheral: "));
// Serial.print(peripheral.localName()); Serial.print(" ("); Serial.print(peripheral.address()); Serial.println(")");
if (!peripheral.discoverAttributes()) {
peripheral.disconnect();
return false;
}
// Serial.println("BLE attributes discovered");
BLECharacteristic multiSensorDataCharacteristic = peripheral.characteristic(BLE_UUID_MULTI_SENSOR_DATA);
if (!multiSensorDataCharacteristic) {
peripheral.disconnect();
return false;
}
// Serial.println("BLE characteristic found");
if (!multiSensorDataCharacteristic.canSubscribe()) {
peripheral.disconnect();
return false;
}
// Serial.println("BLE characteristic can subscribe");
if (!multiSensorDataCharacteristic.subscribe()) {
peripheral.disconnect();
return false;
}
// Serial.println("BLE characteristic subscribed");
while (peripheral.connected()) {
digitalWrite(LED_R_PIN, HIGH); // turn red LED off
digitalWrite(LED_G_PIN, LOW); // turn green LED on
digitalWrite(LED_B_PIN, HIGH); // turn red LED off
unsigned long currentMillis = millis();
if (currentMillis - previousMillis > BLE_POLL_INTERVALL) {
previousMillis = currentMillis;
BLE.poll();
if (multiSensorDataCharacteristic.valueUpdated()) {
multiSensorDataCharacteristic.readValue(myValue);
Serial.print("value = ");
Serial.println(myValue);
}
}
}
peripheral.disconnect();
// Serial.print(F("Disconnected from peripheral: "));
// Serial.print(peripheral.localName()); Serial.print(" ("); Serial.print(peripheral.address()); Serial.println(")");
return true;
}
Some feedback :
remove the line 53 : peripheral = BLEDevice(); // NOT necessary ! move line 58 : BLE.begin(); // to setup. it only needs to be done once NOT multiple times !
regards, Paul
very good - I just identified these two differences as well. Thank you!
The test is running again....
With your code, the Seeed Xiao nRF52840 BLE
froze after approx 1-3 hours unfortunately again (Central-Receiver). I made two tests and both froze.
It looks like for this board, the only workaround remains to switch roles.
I test the other board (Arduino Nano 33 BLE) now....
Bad news also for the Arduino Nano 33 BLE
board: it froze after 2 hours just now.... (again with your code above - i.e. with BLE.scanForUuid(BLE_UUID_SENSOR_DATA_SERVICE, true);
)
So it seems also for this board, the only workaround is to switch roles.
I have just deleted and re-installed a blank new version of ArduinoBLE. So many things tried.. want to make sure we start from scratch.
2 changes applied. in HCI.cpp : // HCI.leSetAdvertiseEnable(0x01); commented out in GAP.cpp : added on line 190 : Serial.println("report"); ( want to see a print) using attached sketch
I have uploaded to BOTH the Apollo3 and nrf52480 MicroMod. Both are running in parallel. NO peripheral active, just the centrals. I see a constant flow of "report" coming in (as I would expect with duplicates).
Instead of overloading github you can also drop me a mail paulvha@hotmail.com
I am using the Xiao BLE nRF52840 from Seed Studio since the size is what I need (i.e. Nano 33 is too large)
However, using BLE Receiver Central, it freezes after 15-30min !!!
The sender is off most of the time and will only connect and send sensor data about once or twice a day. I try to have the receiver active 24h. But it freezes after 15-30min.
Here is my receiver Central code:
I tried to add a periodic BLE.end(); followed by a BLE.begin(); every 10 seconds because I believed this might help. But no change.
It seems that only a module RESET will solve the problem.
However, in my application I would like to have the receiver listen during 24 hours (and never power it off or reset).
What could be the issue of the freeze ?