arduino / ArduinoCore-arc32

GNU Lesser General Public License v2.1
329 stars 284 forks source link

Using Bluetooth and the Adafruit neomatrix library together causes execution to cease #229

Closed ghost closed 7 years ago

ghost commented 8 years ago

If I use CurieBLE to implement a Bluetooth profile and use the Adafruit neomatrix library in the same sketch I find that after my first interaction with the connected neomatrix panel, I no longer receive callbacks to functions from the BLE API and calls to loop() no longer take place. In short execution seems to have halted.

Is this an ISR related issue?

I've attached two sketches. One use the neomatrix library but with no Bluetooth, the other uses the neomatrix library in exactly the same way but is driven by writes to a Bluetooth characteristic. The former one works fine, the latter does not.

To recreate, load the neomatrixdemo sketch, then using any GATT client application like the nRF Master Control Panel app on an Android device, scan for the Genuino, connect to it and then write a value of 0x07 to the first characteristic (UUID: 74ED00018CA349464560B9FB911D898E). You should see a callback to neomatrixservice_StripControlcharacteristicWriteHandler. Now repeat the same test (or try a value of 0x08). You'll get no response and on investigation should find that loop() is no longer being called.

genuino101.zip

SidLeung commented 8 years ago

Did the value matter between the 1st write and the 2nd write? Or just 2 write's hung the system. Thanks.

ghost commented 8 years ago

No, the value didn't seem to matter. You could also use 0x06FF0000 or 0x09 or 0x0A.

Basically I think any use of the neomatrix library causes this problem.

If you look at the code you'll see a function called flashCorners(). It's been turned into an empty stub but the old code is still there. I used to call this function at various points and it too caused the problem so I commented it out in an attempt to isolate the issue, eventually concluding it was a general issue with the combination of BLE use and the neomatrix APIs.

SidLeung commented 8 years ago

Thank you for the info.

ghost commented 8 years ago

Using the non-callback pattern with the CurieBLE API I do not experience this problem. Sample code attached. neomatrixdemo_loop.zip

ghost commented 8 years ago

Update: I think I do experience this problem with the non-callback pattern. It just happens less frequently.

SidLeung commented 8 years ago

Checking this issue with the BLE library under construction to ensure this will not happen in the up coming release.

ghost commented 8 years ago

@SidLeung Great, thank you. So you're saying there's a new Bluetooth low energy library coming? Do you have an ETA for this?

SidLeung commented 8 years ago

Hello, there. I have good news and some suggestions for you. Our team member was able to generate the issue with the info you provided. Allow me to bring up the subject of foreground and background processing in a computing environment. Foreground processing refers to the code execution within an interrupt context. The callback routine invoked by an event is a kind of foreground processing. Interrupt takes priority over the system by context switching out of the background processing. Think of it as if you went into a coffee shop, paused the person behind the counter from what s/he was doing in order to get his/er attention. You can ask for a very quick request or you can talk about the proper way of roasting coffee beans. The former has little impact to the shop and the later may involve the fire department, police, lawyers, etc. You may need to re-arrange the processing in the callback functions to be in the background. The current setup backs up the processing of BLE events and may have contributed to the BLE stack crash issue.

smartyw commented 8 years ago

@SidLeung Glad you recreated the problem.

I mentioned ISR (Interrupt Service Routines) in my initial report if you check.... I had a feeling this was concerned with interrupt servicing and priorities.

I have no control over callbacks from the Bluetooth stack and what context they're executed in however. @sandeepmistry may have some comments on this though.

Furthermore, if you check my more recent posts, I'm seeing what I think is the same issue when not using Bluetooth with the callback pattern. It happens less often but it does still happen. The neomatrix library is probably a factor here.

Good to see you confirm that the Bluetooth stack is crashing. Bad that it's happening of course.

You appear to be pushing the responsibility for resolving the issue onto the application developer (unless I misunderstood) but I would like to point out that no such issues are encountered using the same neomatrix library and same application logic using an Arduino Uno with Red Bear Lab Bluetooth shield.

SidLeung commented 8 years ago

Ok. Fair enough. It is true that I would not like to, and not authorized to, take on the responsibility of debugging 3rd party library without prior approval from my superiors. However, I can try to duplicate the problem with the non-call back sketch you provided in the zip file. Upon successful duplication, I can work request for priority to the issue. I am not all surprised to learn that the crashing problem occurred without the callbacks. If my memory serves me correctly (and not point fingers here), the timing requirement of the neopixel library makes the code to operate at foreground (or you may not get the color you want). That may post a problem with the BLE library - similar to the situation with the callbacks. As for the situation with Red Bear Lab Bluetooth shield, that is a different ball game since you have another processor to take care of the Bluetooth processing. No sharing of processor like the environment you are in with the Arduino 101.

smartyw commented 8 years ago

@SidLeung Good point on the dedicated processor with my Arduino Uno. Yep, makes sense.

It may be the 101 just isn't suitable for my purposes.

Cheers

Martin

noelpaz commented 8 years ago

Hi. Sidney asked me to look into this.

First off my setup IDE = 1.6.11 Arduino 101 1.0.7 NeoPixel at 1.0.6 and Neo Matrix at 1.1.2. I also made sure I had the correct capacitor plus my power supply is 5VDC and 10amp. If you skimp on your power supply you can crash the program.

What I did is I did not use the poll function but rather use the LED example that checks if there is a connection to central. This enabled me to remove the connect and disconnect callbacks Then I moved all the call to the heavy lifting functions out of the strip control callback and instead just set a global int value to define what strip function to run. Then in the loop I just checked what that strip function is as set by this global value. In this way you do not spend so much time in the callback.

the simple() function is causing it to hang but I was able to input from 6 to 10 of the diff strip functions and I can call those with my Phone using nrf Controller.

/*
   Author: Martin Woolley

   Version History
   V1.0:
   First version

*/

#include <CurieBLE.h>

#include <Adafruit_NeoPixel.h>
#define NEOPIXEL_PIN 6
#define ONE_SECOND 1000
#define NEOPIXEL_OFF                  0
#define NEOPIXEL_ON                   1
#define NEOPIXEL_SNAKE_FILL           6
#define NEOPIXEL_RIPPLE_RANDOM_COLOUR 7
#define NEOPIXEL_SPIRAL               8
#define NEOPIXEL_RAINBOW              9
#define NEOPIXEL_THEATRE             10

Adafruit_NeoPixel neopixel_strip;
uint32_t off;
uint8_t strip_status[1];
uint8_t neopixel_led_count = 64;
int8_t globalTcToRun = 0;
bool okToPoll = false;

// BLE objects
BLEPeripheral blePeripheral;

// GAP properties
char device_name[] = "My Device";

// Characteristic Properties
unsigned char neomatrixservice_StripControl_props = BLERead | BLEWrite | 0;
unsigned char neomatrixservice_StripStatus_props = BLENotify | BLERead | 0;
unsigned char neomatrixservice_LEDCount_props = BLERead | BLEWrite | 0;
// Services and Characteristics
BLEService neomatrixservice("74ED00008CA349464560B9FB911D898E");

BLECharacteristic neomatrixservice_StripControl("74ED00018CA349464560B9FB911D898A", neomatrixservice_StripControl_props, 20);

BLECharacteristic neomatrixservice_StripStatus("74ED00028CA349464560B9FB911D898B", neomatrixservice_StripStatus_props, 1);

BLECharacteristic neomatrixservice_LEDCount("74ED00038CA349464560B9FB911D898C", neomatrixservice_LEDCount_props, 1);

void setup() {
  Serial.begin(9600);
  //  while (! Serial); // Wait until Serial is ready
  //  Serial.println("setup()");

  // set advertising packet content
  blePeripheral.setLocalName(device_name);

  // add services and characteristics
  blePeripheral.addAttribute(neomatrixservice);
  blePeripheral.addAttribute(neomatrixservice_StripControl);
  blePeripheral.addAttribute(neomatrixservice_StripStatus);

  blePeripheral.addAttribute(neomatrixservice_LEDCount);
  //  Serial.println("attribute table constructed");

  // connection handler registrations
  //  blePeripheral.setEventHandler(BLEConnected, blePeripheralConnectHandler);
  //  blePeripheral.setEventHandler(BLEDisconnected, blePeripheralDisconnectHandler);
  //  Serial.println("connection event handlers set");

  // characteristic event handler registrations
  neomatrixservice_StripControl.setEventHandler(BLEWritten, neomatrixservice_StripControlcharacteristicWriteHandler);
  neomatrixservice_StripStatus.setEventHandler(BLESubscribed, neomatrixservice_StripStatusnotifySubscribedHandler);
  neomatrixservice_StripStatus.setEventHandler(BLEUnsubscribed, neomatrixservice_StripStatusnotifyUnsubscribedHandler);
  neomatrixservice_LEDCount.setEventHandler(BLEWritten, neomatrixservice_LEDCountcharacteristicWriteHandler);
  //  Serial.println("attribute event handlers set");

  // begin advertising
  blePeripheral.begin();
  //  Serial.println("advertising");
  //  Serial.println("neomatrix demo");

  // initialise neomatrix
  neopixel_strip = Adafruit_NeoPixel(neopixel_led_count, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800);
  off = neopixel_strip.Color(0, 0, 0);
  neopixel_strip.begin();
  neopixel_strip.show();
  flashCorners(255, 0, 0);
  okToPoll = true;

}

void loop() {
  BLECentral central = blePeripheral.central();

  // if a central is connected to peripheral:
  if (central) {
    Serial.print("Connected to central: ");
    // print the central's MAC address:
    Serial.println(central.address());

    // while the central is still connected to peripheral:
    while (central.connected()) {
      if (okToPoll == true ) {
        switch (globalTcToRun)
        {
          case 5:
            okToPoll = false;
            //simple();
            // okToPoll = true;
            Serial.println("simple done");
            break;
          case NEOPIXEL_SNAKE_FILL:
            //      Serial.println("Snake Fill");
            okToPoll = false;
            snakeFill(neomatrixservice_StripControl.value()[1], neomatrixservice_StripControl.value()[2], neomatrixservice_StripControl.value()[3], 50, 1);
            // okToPoll = true;
            Serial.println("Snake Fill done");
            break;
          case NEOPIXEL_RIPPLE_RANDOM_COLOUR:
            //      Serial.println("Ripple Random");
            okToPoll = false;
            rippleRandom(200, 4);
            Serial.println("Ripple Random Done");
            // okToPoll = true;
            break;
          case NEOPIXEL_SPIRAL:
            //      Serial.println("Spiral");
            okToPoll = false;
            spiral(25, 5);
            // okToPoll = true;
            break;
          case NEOPIXEL_RAINBOW:
            //      Serial.println("Rainbow");
            okToPoll = false;
            rainbow(40);
            // okToPoll = true;
            break;
          case NEOPIXEL_THEATRE:
            //      Serial.println("Theatre");
            okToPoll = false;
            theaterChaseRainbow(20);
            break;
          //okToPoll = true;
          default:
            okToPoll = false;
            //flashCorners(0, 255, 0);
            //okToPoll = true;
        }
      }
      neopixel_status_available();
      okToPoll = true;
      globalTcToRun = 1;
      Serial.print("ok to poll Verified Status ");
      Serial.println(globalTcToRun);
    }
//    Serial.print(F("Disconnected from central: "));
//    Serial.println(central.address());
  }

}

// APPLICATION LOGIC WILL LARGELY NEED TO BE IMPLEMENTED IN THESE EVENT HANDLERS

//void blePeripheralConnectHandler(BLECentral& central) {
//  // central mode device has connected
//  //  Serial.print(F("Connected event, central: "));
//  //  Serial.println(central.address());
//  flashCorners(0, 0, 255);
//}
//
//void blePeripheralDisconnectHandler(BLECentral& central) {
//  // central mode device has disconnected
//  //  Serial.print(F("Disconnected event, central: "));
//  //  Serial.println(central.address());
//}

// characteristic event handler registrations
void  neomatrixservice_StripControlcharacteristicWriteHandler(BLECentral& central, BLECharacteristic& characteristic) {
  //  Serial.print(F("neomatrixservice_StripControlcharacteristicWriteHandler, central: "));
  //  Serial.println(central.address());
  neopixel_status_unavailable();
  switch (characteristic.value()[0])
  {
    case 5:
      //simple();
      globalTcToRun = 5;
      Serial.println("Simple");
      break;
    case NEOPIXEL_SNAKE_FILL:
       globalTcToRun = NEOPIXEL_SNAKE_FILL;
      Serial.println("Snake Fill");
      break;
    case NEOPIXEL_RIPPLE_RANDOM_COLOUR:
       globalTcToRun = NEOPIXEL_RIPPLE_RANDOM_COLOUR;
      Serial.println("Ripple Random");
      //rippleRandom(200, 4);
      break;
    case NEOPIXEL_SPIRAL:
       globalTcToRun = NEOPIXEL_SPIRAL;
      Serial.println("Spiral");
      // spiral(25, 5);
      break;
    case NEOPIXEL_RAINBOW:
       globalTcToRun = NEOPIXEL_RAINBOW;
      Serial.println("Rainbow");
      //rainbow(40);
      break;
    case NEOPIXEL_THEATRE:

      globalTcToRun =  NEOPIXEL_THEATRE;
      Serial.println("Theatre");
      // theaterChaseRainbow(20);
      break;
    default:
      globalTcToRun =  1;
      // flashCorners(0, 255, 0);
  }

}
void  neomatrixservice_StripStatusnotifySubscribedHandler(BLECentral& central, BLECharacteristic& characteristic) {
  //  Serial.print(F("neomatrixservice_StripStatusnotifySubscribedHandler, central: "));
  //  Serial.println(central.address());
}
void  neomatrixservice_StripStatusnotifyUnsubscribedHandler(BLECentral& central, BLECharacteristic& characteristic) {
  //  Serial.print(F("neomatrixservice_StripStatusnotifyUnsubscribedHandler, central: "));
  //  Serial.println(central.address());
}
void  neomatrixservice_LEDCountcharacteristicWriteHandler(BLECentral& central, BLECharacteristic& characteristic) {
  //  Serial.print(F("neomatrixservice_LEDCountcharacteristicWriteHandler, central: "));
  //  Serial.println(central.address());
}

// Application Code

void simple() {
  uint32_t test_colour = neopixel_strip.Color(255, 0, 0);
  uint32_t off_colour = neopixel_strip.Color(0, 0, 0);
  neopixel_strip.setPixelColor(0, test_colour);
  delay(250);
}

void flashCorners(uint8_t red, uint8_t green, uint8_t blue) {
}

/*
  void flashCorners(uint8_t red,uint8_t green,uint8_t blue) {
  //  Serial.println("Flashing corners");
  all_LEDs_off();
  uint32_t on_colour = neopixel_strip.Color(red, green, blue);
  neopixel_strip.setPixelColor(0, on_colour);
  neopixel_strip.setPixelColor(7, on_colour);
  neopixel_strip.setPixelColor(56, on_colour);
  neopixel_strip.setPixelColor(63, on_colour);
  neopixel_strip.show();
  delay(250);
  neopixel_strip.setPixelColor(0,off);
  neopixel_strip.setPixelColor(7,off);
  neopixel_strip.setPixelColor(56,off);
  neopixel_strip.setPixelColor(63,off);
  neopixel_strip.show();
  }
*/
void setPixelColors(uint8_t *leds, uint8_t led_count, uint32_t colour) {
  for (int i = 0; i < led_count; i++) {
    if (leds[i] < neopixel_led_count) {
      neopixel_strip.setPixelColor(leds[i], colour);
    }
  }
}

// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
uint32_t Wheel(byte WheelPos) {
  WheelPos = 255 - WheelPos;
  if (WheelPos < 85) {
    return neopixel_strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  }
  if (WheelPos < 170) {
    WheelPos -= 85;
    return neopixel_strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  }
  WheelPos -= 170;
  return neopixel_strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}

//Theatre-style crawling lights with rainbow effect
void theaterChaseRainbow(uint8_t wait) {
  for (int j = 0; j < 256; j++) {   // cycle all 256 colors in the wheel
    for (int q = 0; q < 3; q++) {
      for (int i = 0; i < neopixel_led_count; i = i + 3) {
        if ((i + q) < neopixel_led_count) {
          neopixel_strip.setPixelColor(i + q, Wheel( (i + j) % 255)); //turn every third pixel on
        }
      }
      neopixel_strip.show();
      delay(wait);
      for (int i = 0; i < neopixel_led_count; i = i + 3) {
        neopixel_strip.setPixelColor(i + q, 0);      //turn every third pixel off
      }
    }
  }
  all_LEDs_off();
}

void rainbow(uint8_t wait) {
  uint16_t i, j;
  for (j = 0; j < 256; j++) {
    for (i = 0; i < neopixel_led_count; i++) {
      neopixel_strip.setPixelColor(i, Wheel((i + j) & 255));
    }
    neopixel_strip.show();
    delay(wait);
  }
  all_LEDs_off();
}

void snakeFill(uint8_t red, uint8_t green, uint8_t blue, uint8_t wait, uint8_t repetitions) {
  all_LEDs_off();
  uint32_t color = neopixel_strip.Color(red, green, blue);
  for (uint8_t reps = 0; reps < repetitions; reps++) {
    for (uint8_t on_off = 0; on_off < 2; on_off++) {
      int i = 0;
      int d = 1;
      int led = 0;
      int count = 0;
      while (count < neopixel_led_count) {
        neopixel_strip.setPixelColor(led, color);
        neopixel_strip.show();
        delay(wait);
        i++;
        count++;
        if (i == 8) {
          d = d * -1;
          led = led + 8;
          i = 0;
        } else {
          led = led + d;
        }
      }
      // now do it again but switching LEDs off
      color = off;
    }
    neopixel_strip.setPixelColor(0, 0, 0, 0);
    neopixel_strip.show();
  }
}

void all_LEDs_off()
{
  for (int i = 0; i < neopixel_led_count; i++) {
    neopixel_strip.setPixelColor(i , off);
  }
  neopixel_strip.show();
}

void spiral(uint8_t wait, uint8_t cycles) {
  uint8_t square_sizes [4] = {4, 12, 20, 28};
  uint8_t s1 [ 4] = {27, 28, 36, 35};
  uint8_t s2 [12] = {34, 26, 18, 19, 20, 21, 29, 37, 45, 44, 43, 42};
  uint8_t s3 [20] = { 9, 10, 11, 12, 13, 14, 22, 30, 38, 46, 54, 53, 52, 51, 50, 49, 41, 33, 25, 17};
  uint8_t s4 [28] = { 1, 2, 3, 4, 5, 6, 7, 15, 23, 31, 39, 47, 55, 63, 62, 61, 60, 59, 58, 57, 56, 48, 40, 32, 24, 16, 8, 0};

  uint8_t* led_data_table [4];
  led_data_table[0] = s1;
  led_data_table[1] = s2;
  led_data_table[2] = s3;
  led_data_table[3] = s4;

  for (uint8_t c = 0; c < cycles; c++) {
    uint8_t random_R = (uint8_t) random(128);
    uint8_t random_G = (uint8_t) random(128);
    uint8_t random_B = (uint8_t) random(128);
    uint32_t random_colour = neopixel_strip.Color(random_R, random_G, random_B);

    for (uint8_t i = 0; i < 4; i++) {
      for (int j = 0; j < square_sizes[i]; j++) {
        if (led_data_table[i][j] < neopixel_led_count) {
          neopixel_strip.setPixelColor(led_data_table[i][j], random_colour);
          neopixel_strip.show();
          delay(wait);
        }
      }
    }
    for (uint8_t i = 0; i < 4; i++) {
      for (int j = 0; j < square_sizes[i]; j++) {
        if (led_data_table[i][j] < neopixel_led_count) {
          neopixel_strip.setPixelColor(led_data_table[i][j], off);
          neopixel_strip.show();
          delay(wait);
        }
      }
    }
  }
}

void rippleRandom(uint8_t wait, uint8_t cycles) {
  uint8_t square_sizes [4] = {4, 12, 20, 28};
  uint8_t s1 [ 4] = {27, 28, 36, 35};
  uint8_t s2 [12] = {18, 19, 20, 21, 29, 37, 45, 44, 43, 42, 34, 26};
  uint8_t s3 [20] = { 9, 10, 11, 12, 13, 14, 22, 30, 38, 46, 54, 53, 52, 51, 50, 49, 41, 33, 25, 17};
  uint8_t s4 [28] = { 0, 1, 2, 3, 4, 5, 6, 7, 15, 23, 31, 39, 47, 55, 63, 62, 61, 60, 59, 58, 57, 56, 48, 40, 32, 24, 16, 8};
  uint8_t* led_data_table [4];
  led_data_table[0] = s1;
  led_data_table[1] = s2;
  led_data_table[2] = s3;
  led_data_table[3] = s4;

  for (uint8_t c = 0; c < cycles; c++) {
    for (uint8_t i = 0; i < 4; i++) {
      uint8_t random_R = (uint8_t) random(128);
      uint8_t random_G = (uint8_t) random(128);
      uint8_t random_B = (uint8_t) random(128);
      uint32_t random_color = neopixel_strip.Color(random_R, random_G, random_B);
      setPixelColors(led_data_table[i], square_sizes[i], random_color);
      neopixel_strip.show();
      delay(wait);
    }
    uint8_t i = 3;
    for (uint8_t j = 0; j < 4; j++) {
      setPixelColors(led_data_table[i], square_sizes[i], off);
      i--;
      neopixel_strip.show();
      delay(wait);
    }
  }
}

void neopixel_status_available() {
  strip_status[0] = 0;
  neomatrixservice_StripStatus.setValue(strip_status, 1);
}

void neopixel_status_unavailable() {
  strip_status[0] = 1;
  neomatrixservice_StripStatus.setValue(strip_status, 1);
}
ghost commented 8 years ago

Hi @noelpaz

thanks for looking at this. In fact I'm already doing something very similar:

    while (central.connected()) {

        if (neomatrixservice_StripControl.written()) {
        // application logic for handling WRITE or WRITE_WITHOUT_RESPONSE on characteristic neomatrix service Strip Control goes here
            neopixel_status_unavailable();
            switch (neomatrixservice_StripControl.value()[0]) 
            {
            case NEOPIXEL_SNAKE_FILL:
                snakeFill(neomatrixservice_StripControl.value()[1],neomatrixservice_StripControl.value()[2],neomatrixservice_StripControl.value()[3], 50,1);
                break;
              case NEOPIXEL_RIPPLE_RANDOM_COLOUR: 
                rippleRandom(200,4);
                break;
              case NEOPIXEL_SPIRAL:
                spiral(25,5);
                break;
              case NEOPIXEL_RAINBOW:
                rainbow(40);
                break;
              case NEOPIXEL_THEATRE:
                theaterChaseRainbow(20);
                break;
              default:
                flashCorners(0,255,0);
            }        
            neopixel_status_available();
          }

What I've found is that execution is significantly more reliable with this approach but I still encounter 'the problem' about one time in 6 or something like that. I guess it's not deterministic and timing is a factor so refactoring the application code is probably never going to solve this fully, just tweak probabilities.

Thanks again, appreciate you looking at this.

Martin

SidLeung commented 7 years ago

Hi, Martin:

One more piece of info you may find interesting. Intel is on its way to enable user to utilize both cores in the Curie module, namely, the Quark SE and the ARC core. The project is called Curie Open Developer Kit (CODK). Inside the kit, you will find a s/w development environment that offers multiple ways to make use of the two Cores. You may want to take a look at it.

Sid

ghost commented 7 years ago

Thanks Sid, that sounds interesting :-)

kitsunami commented 7 years ago

In reference to Sidney's comment, you can find the software complete solution here: www.intel.com/curieodk