Low power I2C BLE beacon,

Hi everyone,

Looking for some guidance on the following please. I’m 95% there but just can’t tie the pieces together.

I have a XIOA BLE and I’m using arduino ide. I’m looking to build something that works like this.

  • Acts as an I2C slave, accepting 11 Bytes packets from another device
  • Advertises this data by BLE with an interval of ~10 seconds
  • Uses the lowest possible power

All the individual bits are working.

  • I can read/write the I2C buffer from external devices on the bus
  • BLE advertising is working, reading data from the buffer when updateAdvertisingData() is called
  • Low power is working, average of 40uA

The problem is that I can’t tie them together.

When I call updateAdvertisingData() from inside the I2C event handler, the device crashes.

If I try to use a global flag dataUpdated = true; and conditionally check that in a loop, it works however I then lose low power operation, presumably because the cpu stays on, running at 7mA

void loop() {
  // Check if data has been updated via I2C
  if (dataUpdated) {
    updateAdvertisingData();
    dataUpdated = false;
  }
}

I’m not sure how or where I should call updateAdvertisingData() so that I can keep the device in low power.

Any pointers would be appreciated.

Thanks

#include <bluefruit.h>
#include <Wire.h>
#include "nrf_nvic.h"
#include "nrf_soc.h"

#define I2C_ADDRESS 0x10
volatile bool dataUpdated = false;

volatile uint8_t i2cBuffer[22]; // 22 bytes for 11 integers

void receiveEvent(int howMany) {
  if (howMany > sizeof(i2cBuffer)) {
    while (Wire.available()) {
      Wire.read();
    }
    return;
  }

  for (int i = 0; i < howMany && i < sizeof(i2cBuffer); i++) {
    i2cBuffer[i] = Wire.read();
  }

  // Debug print to show received data
  Serial.print("Data received: ");
  for (int i = 0; i < howMany; i++) {
    Serial.print(i2cBuffer[i], HEX);
    Serial.print(" ");
  }
  Serial.println();

  // Flag that data has been updated
  dataUpdated = true;

}

void requestEvent() {
  const uint8_t* bufferPtr = (const uint8_t*)i2cBuffer;
  Wire.write(bufferPtr, sizeof(i2cBuffer));

  // Debug print to show sent data
  Serial.print("Data sent: ");
  for (int i = 0; i < sizeof(i2cBuffer); i++) {
    Serial.print(bufferPtr[i], HEX);
    Serial.print(" ");
  }
  Serial.println();
}

void updateAdvertisingData() {
  Serial.println("Updating advertising data...");

  uint8_t msd[25]; 
  msd[0] = 0xFF; // Manufacturer specific data type
  msd[1] = 0x00; // Manufacturer ID (low byte)
  msd[2] = 0x00; // Manufacturer ID (high byte)
  memcpy(&msd[3], (const void*)i2cBuffer, sizeof(i2cBuffer));

  // Debug print to show the data to be advertised
  Serial.print("Data to be advertised: ");
  for (int i = 0; i < sizeof(msd); i++) {
    Serial.print(msd[i], HEX);
    Serial.print(" ");
  }
  Serial.println();
  Bluefruit.Advertising.clearData();
  Bluefruit.Advertising.addData(BLE_GAP_AD_TYPE_MANUFACTURER_SPECIFIC_DATA, msd, sizeof(msd));
  Bluefruit.ScanResponse.addName();
  Bluefruit.Advertising.restartOnDisconnect(true);
  Bluefruit.Advertising.setInterval(16000, 16000); // Advertising every 10 seconds
  Bluefruit.Advertising.setFastTimeout(30); // Fast advertising for 30 seconds
  Bluefruit.Advertising.start(0);
  
  // Debug print to show updated advertising data
  Serial.print("Advertising data updated: ");
  for (int i = 0; i < sizeof(msd); i++) {
    Serial.print(msd[i], HEX);
    Serial.print(" ");
  }
  Serial.println();

}

void setup() {
  Serial.begin(115200);

  Serial.println("I2C and BLE Example");

  Wire.begin(I2C_ADDRESS);
  Wire.setPins(4, 5); // SDA on P0.04, SCL on P0.05
  Wire.onReceive(receiveEvent);
  Wire.onRequest(requestEvent);

  Serial.println("I2C Slave Initialized");

  // Initialize BLE
  Bluefruit.begin();
  Bluefruit.setName("MY12345");
  Bluefruit.autoConnLed(false);
  Bluefruit.setTxPower(-4); // Reduce TX power to save energy

  // Start advertising with initial data
  updateAdvertisingData();

  Serial.println("Broadcasting, open your beacon app to test");

  // Suspend the loop to save power
  suspendLoop();

}


void loop() {

}

This is not a direct answer, but the sketch in post #31 in the link below may be helpful.

1 Like

What do you mean by crash… did it brown out? you may have to go to full power to feed the radio

Thank you this helped a lot, all I needed to do was this

void loop() {
  // Check if data has been updated via I2C

    updateAdvertisingData();
    delay(10 * 1000);

}

I wasn’t aware that the delay function would result in low power operation!

By crash I mean the device stops responding to I2C commands and the power consumption jumps up to 7mA, no output on the serial console.

I’m assuming that it doesn’t like me calling updateAdvertisingData() from inside the interrupt handler, because the function updateAdvertisingData() works as expected when called from other parts of the code and also the interrupt handler works correctly when updateAdvertisingData() isn’t called.

Hi there,
Second rule of interrupt ISR routines, keep them short set a flag and leave let the main loop interrogate the Flag clean and simple, Check out the IMU code Tap to sleep example on here, Note the ISR’s for the tap to sleep and the wakeup, The delay is required for Sleep ,to settle the edge detection for the INT.
after attaching the pin.
HTH
GL :slight_smile: PJ
:v:

1 Like

Thanks! What’s the first rule?

… yeah… what is the first rule… never talk about ISR’s…

Hi there,
there is a long standing debate on the first rule, can be the second rule also.
But I was taught long ago

Use flags and queues

Use simple flags to indicate that an event has occurred, and then use queues to pass data from the ISR to other threads. This allows the ISR to quickly return to handling interrupts while other threads process the data
Here are some rules for using interrupt service routines (ISRs) in embedded systems:

  • Keep it short

ISRs should be as short as possible, ideally half a page of C code or less. ISR execution time should also be short, ideally 100–200 clock cycles or less.

  • Avoid loops

Loops can make worst case scenarios more difficult.

  • Don’t waste time

Don’t put in wait loops for hardware responses or other time-consuming processing.

  • Don’t re-enable interrupts

Re-enabling interrupts within an ISR can cause race conditions and stack overflow problems.

Some other things to consider in more of a style than rule.

Interrupt Handler Rules of Thumb

  • Don’t declare any non-static variables inside the handler.
  • Avoid blocking function calls.
  • Avoid non-reentrant function calls.
  • Avoid any processing that takes non-trivial time.
  • Avoid operations with locks as you can deadlock your program in an ISR.

Hit this read for more 7 of them but pretty much the same for any embedded system

HTH
GL :slight_smile: PJ
:v:

1 Like