Adafruit Bluetooth LE SPI “Friend” with data loss and stalls (infinite loops)
I’m working on adding bluetooth support to my LED cyr wheel. A long time ago I picked up an Adafruit nRF8001 breakout board — I was worried it was too big to fit in my wheel, and once I started working with it I realized it sucked. Specifically, the API was limiting; you couldn’t even set the peripheral’s name to something longer than 8 characters! Luckily they now have an “Bluefruit SPI Friend”, so I got one of those. Well, I ended up accidentally getting two. The first one I had sent via USPS, and it took 3 weeks to arrive! I thought it was lost, and I had to order another one via UPS 3 day shipping, and of course…they both arrive on the same day.
The Bluefruit LE Friend is interesting. Adafruit’s produce page doesn’t even mention what chip it runs. You can discover it if you read their “intro page”; a long ways down they mention a nRF51822 flash updated, and you realize it is a nRF51 based ship from Nordic. I’m hoping they will fix their page to be a little more clear on what is being used. The way it works is they created some closed source code that runs on the nRF51822 chip using the Nordic SDK. Their code talks to your microprocessor (Teensy/Arduino/etc) via SPI or UART. I picked SPI, because I thought it would be better and faster…but their implementation basically forces it to be UART. I was really hoping for regular callbacks for characteristic updates…but you don’t get those. You have to poll it using “AT” commands, and then parse the AT response. This is horribly slow, and the underlying SPI mechanism uses some special “Simple Data Transport” mechanism that transfers at most a 16 byte payload at a time. That is not good considering bluetooth transfers 20 bytes max at a time. Well, you can do more if you have more control, like from iPhone to iPhone…but not so with the nRF51 from what I can tell.
The only way for you to quickly read data with the Bluefruit LE friend is to use their UART service which provides a transmit and receive characteristic. When you do this, the SPI protocol lets you receive packets at their SPI transfer packet size (yeah, just 16 bytes of “payload”, which is terrible..). I figured I’d try it out.
I wrote a Mac app that connects to the Bluefruit LE. I chunk up data into 20 byte sizes, and send it (until I’m done) in: (Swift code):
func peripheral(peripheral: CBPeripheral, didWriteValueForCharacteristic characteristic: CBCharacteristic, error: NSError?) {
if characteristic == _uartTransmitCharacteristic {
let dataLeft = _dataToSend!.length – _dataOffset;
if dataLeft > 0 {
let amountToSend = min(dataLeft, _dataChunkSize)
let subData: NSData = _dataToSend!.subdataWithRange(NSMakeRange(_dataOffset, amountToSend))
_dataOffset += subData.length
peripheral.writeValue(subData, forCharacteristic: _uartTransmitCharacteristic!, type: CBCharacteristicWriteType.WithResponse)
}
}
}
In my Teensy/Arduino code, I would read it. This a pure hack for testing the sending of data:
if (m_ble.available()) {
int count = 0;
uint32_t start = millis();
// Read in the size
int32_t totalBytes = 0;
m_ble.readBytes((char*)&totalBytes, sizeof(totalBytes)); // skanky!
#define bufferSize 1024
uint8_t buffer[bufferSize];
DEBUG_PRINTF(“totalBytes: %d\r\n”, totalBytes);
if (totalBytes > 0) {
int sizeLeft = totalBytes;
uint32_t lastReadTime = millis();
while (sizeLeft > 0) {
while (!m_ble.available()) {
if (millis() – lastReadTime > BT_FILE_READ_TIMEOUT) {
DEBUG_PRINTF(“timeout! read: %d/%d\r\n”, count, totalBytes);
sizeLeft = 0;
break;
// TODO: errors
}
}
int read = 0;
while (m_ble.available()) {
int amountToRead = min(sizeLeft, bufferSize);
int amountRead = m_ble.readBytes(buffer, amountToRead);
// DEBUG_PRINTF(“%c”, m_ble.read());
count += amountRead;
sizeLeft -= amountRead;
lastReadTime = millis();
read += amountRead;
}
}
}
uint32_t time = millis() – start;
DEBUG_PRINTF(” read %d, time took: %d ms, %g s\r\n”, count, time, time /1000.0 );
}
This works, but I was frequently seeing my Teensy “lock up”. I realized some code was infinite looping in the SPI layer…but adding in logs was allowing it to work. This told me they had timing issues with a fast chip. Doh! But…even so packets wouldn’t come complete..and guess what — they were 16 of the 20 bytes I was expecting! So, clearly this was something in the Bluefruit SPI layer.
I don’t really know SPI, but I took the time to learn how all their code worked, and figured out what was wrong. It makes sense, and I fixed it on a clone/fork:
https://github.com/corbinstreehouse/Adafruit_BluefruitLE_nRF51
I sent them a pull request.. we’ll see what happens!
For everyone else…use my code.
Unfortunately, this mechanism is too slow. It takes 9.5-10 seconds to transfer 1kB. 270kB files would take 45 minutes to send over! So, I’m looking into faster options..which I think means writing custom firmware with a RedBearLab BLE Nano.