Goal:
Hi, I’m trying to send an image from my phone via Bluetooth, to my Adafruit Bluefruit nRF52832 feather, so it can save the image on an SD card and then display it on a GC9A01 display.
What works:
I can currently send images to the Feather using Adafruit’s app. I’m using their example sketch, image_transfer.ino. Their sketch is supposed to take the pixel data and display it on a tft display. I’ve removed the display code and included the SD.h library, so I can save the data I’m receiving on a microSD card. However, when I pop the SD card into my laptop, I can’t open the saved files. I’ve tried saving them as .jpgs and .bmps. I took a look at the app’s source code, and it looks like it sends the files as a Bitmap but having the file as a .jpg would be ideal.
The issue:
I used a hex editor to see what was being saved, and only the pixel data is being sent over. I believe the reason I can’t open the files is because they don’t have the proper headers.
I think a possible solution to this would be to generate the headers for these files, but I’m not quite sure how to do that. Any guidance on that or any other ideas on how to get the images to open would much appreciated.
What I'm using:
-
App: BlueFruit LE Connect for Android by Adafruit
-
Board: Adafruit Bluefruit nRF52832 feather
-
Display: GC9A01 1.28 Inch Round LCD Module
-
Every image sent will be 24-bit with a 240 x 240 resolution
/*********************************************************************
This is an example for our nRF52 based Bluefruit LE modules
Pick one up today in the adafruit shop!
Adafruit invests time and resources providing this open source code,
please support Adafruit and open-source hardware by purchasing
products from Adafruit!
MIT license, check LICENSE for more information
All text above, and the splash screen below must be included in
any redistribution
*********************************************************************/
#include <bluefruit.h>
#include <SPI.h>
#include <SD.h>
//#include <Adafruit_GFX.h>
/* This sketch demonstrates the "Image Upload" feature of Bluefruit Mobile App.
Following TFT Display are supported
- TFT 3.5" : FeatherWing https://www.adafruit.com/product/3651
- TFT 2.4" : FeatherWing https://www.adafruit.com/product/3315
- TFT Gizmo : https://www.adafruit.com/product/4367
- Adafruit CLUE : https://www.adafruit.com/product/4500
*/
// CLUE use on-board TFT
#define DEVICE_NAME "Feather"
// [Configurable] For other boards please select which external display to match your hardware setup
// Universal color
#define COLOR_WHITE 0xFFFF
#define COLOR_BLACK 0x0000
#define COLOR_YELLOW 0xFFE0
#define COLOR_GREEN 0x07E0
#define COLOR_RED 0xF800
// Declaring Uart over BLE with large buffer to hold image data
// Depending on the Image Resolution and Transfer Mode especially without response
// or Interleaved with high ratio. You may need to increase this buffer size
BLEUart bleuart(10 * 1024);
/* The Image Transfer module sends the image of your choice to Bluefruit LE over UART.
Each image sent begins with
- A single byte char '!' (0x21) followed by 'I' helper for image
- Color depth: 24-bit for RGB 888, 16-bit for RGB 565
- Image width (uint16 little endian, 2 bytes)
- Image height (uint16 little endian, 2 bytes)
- Pixel data encoded as RGB 16/24 bit and suffixed by a single byte CRC.
Format: [ '!' ] [ 'I' ] [uint8_t color bit] [ uint16 width ] [ uint16 height ] [ r g b ] [ r g b ] [ r g b ] … [ CRC ]
*/
uint16_t imageWidth = 0;
uint16_t imageHeight = 0;
uint8_t imageColorBit = 0;
uint32_t totalPixel = 0; // received pixel
// pixel line buffer, should be large enough to hold an image width
uint16_t pixel_buf[512];
// Statistics for speed testing
uint32_t rxStartTime = 0;
uint32_t rxLastTime = 0;
// for print out message to TFT once
bool bleuart_overflowed = false;
File myFile;
void setup()
{
Serial.begin(115200);
// tft.setTextSize(1);
// Config the peripheral connection with maximum bandwidth
// more SRAM required by SoftDevice
// Note: All config***() function must be called before begin()
Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);
Bluefruit.begin();
Bluefruit.setTxPower(4); // Check bluefruit.h for supported values
Bluefruit.Periph.setConnectCallback(connect_callback);
Bluefruit.Periph.setDisconnectCallback(disconnect_callback);
Bluefruit.Periph.setConnInterval(6, 12); // 7.5 - 15 ms
// Configure and Start BLE Uart Service
bleuart.begin();
if (!SD.begin(11)) {
Serial.println("initialization failed!");
// while (1);
}
Serial.println("initialization done.");
myFile = SD.open("received.jpg", FILE_WRITE);
// Due to huge amount of image data
// NRF52832 doesn't have enough SRAM to queue up received packets using deferred callbacks.
// Therefore it must process data as soon as it comes, this can be done by
// changing the default "deferred" option to false to invoke callback immediately.
// However, the transfer speed will be affected since immediate callback will block BLE task
// to process data especially when tft.drawRGBBitmap() is calling.
// 2nd argument is false to invoke callbacks immediately (thus blocking other ble events)
bleuart.setRxCallback(bleuart_rx_callback, false);
bleuart.setRxOverflowCallback(bleuart_overflow_callback);
// Set up and start advertising
startAdv();
// tft.println("Advertising ... ");
}
void startAdv(void)
{
Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
Bluefruit.Advertising.addTxPower();
Bluefruit.Advertising.addAppearance(BLE_APPEARANCE_GENERIC_CLOCK);
// Include bleuart 128-bit uuid
Bluefruit.Advertising.addService(bleuart);
// There is no room for Name in Advertising packet
// Use Scan response for Name
Bluefruit.ScanResponse.addName();
/* Start Advertising
- Enable auto advertising if disconnected
- Interval: fast mode = 20 ms, slow mode = 152.5 ms
- Timeout for fast mode is 30 seconds
- Start(timeout) with timeout = 0 will advertise forever (until connected)
For recommended advertising interval
https://developer.apple.com/library/content/qa/qa1931/_index.html
*/
Bluefruit.Advertising.restartOnDisconnect(true);
Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms
Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode
Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds
}
void loop()
{
// nothing to do
}
// Invoked when receiving data from bleuart
// Pull data from bleuart fifo & draw image as soon as possible,
// Otherwise bleuart fifo can be overflowed
void bleuart_rx_callback(uint16_t conn_hdl)
{
(void) conn_hdl;
rxLastTime = millis();
// Received new Image
if ( (imageWidth == 0) && (imageHeight == 0) )
{
// take note of time of first packet
rxStartTime = millis();
// Skip all data until '!I' is found
Serial.println("Line 165 - Before while loop");
while ( bleuart.available() && bleuart.read() != '!' ) {
// Serial.println("In While Loop");
// Doesn't seem to ever hit this while loop
}
Serial.println("After while loop");
Serial.println("Line 175");
if (bleuart.read() != 'I') return;
Serial.println("Line 177");
if ( !bleuart.available() ) return;
imageColorBit = bleuart.read8();
imageWidth = bleuart.read16();
imageHeight = bleuart.read16();
myFile.write(imageColorBit);
myFile.write(imageWidth);
myFile.write(imageHeight);
totalPixel = 0;
// Print out the current connection info
BLEConnection* conn = Bluefruit.Connection(conn_hdl);
Serial.printf("Connection Info: PHY = %d Mbps, Conn Interval = %.2f ms, Data Length = %d, MTU = %d\n",
conn->getPHY(), conn->getConnectionInterval() * 1.25f, conn->getDataLength(), conn->getMtu());
Serial.printf("Receiving an %dx%d Image with %d-bit color\n", imageWidth, imageHeight, imageColorBit);
}
// Extract pixel data to buffer and draw image line by line
while ( bleuart.available() >= 3 )
{
// TFT FeatherWing use 16-bit RGB 655 color, need to convert if input is 24-bit color
if ( imageColorBit == 24 )
{
/**
* orginally part of the sketch
uint8_t rgb[3];
bleuart.read(rgb, 3);
**/
uint8_t rgb;
rgb = (uint8_t) bleuart.read();
// myFile.print(rgb);
// myFile.println(rgb);
myFile.write(rgb);
// pixel_buf[totalPixel % imageWidth] = ((rgb[0] & 0xF8) << 8) | ((rgb[1] & 0xFC) << 3) | (rgb[2] >> 3);
// Serial.println(pixel_buf);
}
else if ( imageColorBit == 16 )
{
// native 16-bit 655 color
pixel_buf[totalPixel % imageWidth] = bleuart.read16();
}
totalPixel++;
// Serial.println(totalPixel);
// Serial.println(imageWidth);
// Serial.println(imageHeight);
// have enough to draw an image line
// if ( (totalPixel % imageWidth) == 0 )
// {
//This is where drawing happens
// tft.drawRGBBitmap(0, totalPixel/imageWidth, pixel_buf, imageWidth, 1);
// }
}
// all pixel data is received
if (totalPixel == 172799)//( totalPixel == imageWidth * imageHeight )
{
uint8_t crc = bleuart.read();
(void) crc;
// do checksum later
// print speed summary
print_summary(totalPixel * (imageColorBit / 8) + 8, rxLastTime - rxStartTime);
// reset and waiting for new image
myFile.close();
Serial.println("closed the sd card");
imageColorBit = 0;
imageWidth = imageHeight = 0;
totalPixel = 0;
}
}
void connect_callback(uint16_t conn_handle)
{
// tft.println("Connected");
// tft.setTextColor(COLOR_GREEN);
// tft.println("Ready to receive new image");
// tft.setTextColor(COLOR_WHITE);
}
void print_summary(uint32_t count, uint32_t ms)
{
float sec = ms / 1000.0F;
// Print to serial
Serial.printf("Received %d bytes in %.2f seconds\n", count, sec);
Serial.printf("Speed: %.2f KB/s\n\n", (count / 1024.0F) / sec);
Serial.println("Ready to receive new image");
}
void bleuart_overflow_callback(uint16_t conn_hdl, uint16_t leftover)
{
(void) conn_hdl;
(void) leftover;
Serial.println("BLEUART rx buffer OVERFLOWED!");
Serial.println("Please increase buffer size for bleuart");
// only print the first time this occur, need disconnect to reset
if (!bleuart_overflowed)
{
// tft.setCursor(0, imageHeight+5);
//
// tft.setTextColor(COLOR_RED);
// tft.println("BLEUART rx buffer OVERFLOWED!");
//
// tft.setTextColor(COLOR_WHITE);
// tft.print("Please increase buffer size for bleuart");
}
bleuart_overflowed = true;
}
/**
invoked when a connection is dropped
@param conn_handle connection where this event happens
@param reason is a BLE_HCI_STATUS_CODE which can be found in ble_hci.h
*/
void disconnect_callback(uint16_t conn_handle, uint8_t reason)
{
(void) conn_handle;
(void) reason;
// tft.fillScreen(COLOR_BLACK);
// tft.setCursor(0, 0);
// tft.println("Advertising ...");
imageColorBit = 0;
imageWidth = imageHeight = 0;
totalPixel = 0;
bleuart_overflowed = false;
bleuart.flush();
}```