Skip to content

Commit cc69ecc

Browse files
authored
Merge pull request #429 from adafruit/develop
Added image eink upload
2 parents 70d59dc + 88a4afe commit cc69ecc

File tree

7 files changed

+424
-35
lines changed

7 files changed

+424
-35
lines changed

.github/workflows/githubci.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ jobs:
5151
BSP_VERSION=`eval ls $HOME/$BSP_PATH`
5252
rm -r $HOME/$BSP_PATH/*
5353
ln -s $GITHUB_WORKSPACE $HOME/$BSP_PATH/$BSP_VERSION
54-
arduino-cli lib install "Adafruit NeoPixel" "Adafruit NeoMatrix" "Adafruit GFX Library" "Adafruit SSD1306" "MIDI Library" "Adafruit ILI9341" "Adafruit HX8357 Library" "Adafruit Circuit Playground" "Firmata"
54+
# Install library dependency
55+
arduino-cli lib install "Adafruit GFX Library" "Adafruit SSD1306" "Adafruit ILI9341" "Adafruit HX8357 Library" "Adafruit EPD"
56+
arduino-cli lib install "Adafruit Circuit Playground" "Adafruit NeoPixel" "Adafruit NeoMatrix" "MIDI Library" "Firmata"
5557
5658
- name: Build examples
5759
run: python3 tools/build_all.py ${{ matrix.arduino-platform }}

cores/nRF5/utility/SoftwareTimer.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,16 @@ void SoftwareTimer::begin(uint32_t ms, TimerCallbackFunction_t callback, void* t
3939
_handle = xTimerCreate(NULL, ms2tick(ms), repeating, timerID, callback);
4040
}
4141

42+
void SoftwareTimer::setID(void* id)
43+
{
44+
vTimerSetTimerID(_handle, id);
45+
}
46+
47+
void* SoftwareTimer::getID(void)
48+
{
49+
return pvTimerGetTimerID(_handle);
50+
}
51+
4252
bool SoftwareTimer::start(void)
4353
{
4454
bool ret = true;

cores/nRF5/utility/SoftwareTimer.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ class SoftwareTimer
5050
void begin(uint32_t ms, TimerCallbackFunction_t callback, void* timerID = NULL, bool repeating = true);
5151
TimerHandle_t getHandle(void) { return _handle; }
5252

53+
void setID(void* id);
54+
void* getID(void);
55+
5356
bool start(void);
5457
bool stop (void);
5558
bool reset (void);
Lines changed: 336 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,336 @@
1+
/*********************************************************************
2+
This is an example for our nRF52 based Bluefruit LE modules
3+
4+
Pick one up today in the adafruit shop!
5+
6+
Adafruit invests time and resources providing this open source code,
7+
please support Adafruit and open-source hardware by purchasing
8+
products from Adafruit!
9+
10+
MIT license, check LICENSE for more information
11+
All text above, and the splash screen below must be included in
12+
any redistribution
13+
*********************************************************************/
14+
15+
#include <bluefruit.h>
16+
#include <SPI.h>
17+
#include <Adafruit_GFX.h>
18+
#include "Adafruit_EPD.h"
19+
20+
/* This sketch demonstrates the "Image Upload" feature of Bluefruit Mobile App.
21+
* Following TFT Display are supported
22+
* - https://www.adafruit.com/product/4428
23+
*/
24+
25+
#define EINK_GIZMO 1
26+
27+
// one of above supported TFT add-on
28+
#define DISPLAY_IN_USE EINK_GIZMO
29+
30+
#define EPD_CS 0
31+
#define EPD_DC 1
32+
#define SRAM_CS -1
33+
#define EPD_RESET PIN_A3 // can set to -1 and share with microcontroller Reset!
34+
#define EPD_BUSY -1 // can set to -1 to not use a pin (will wait a fixed delay)
35+
36+
Adafruit_IL0373 display(152, 152, EPD_DC, EPD_RESET, EPD_CS, SRAM_CS, EPD_BUSY);
37+
38+
39+
// Declaring Uart over BLE with large buffer to hold image data
40+
// Depending on the Image Resolution and Transfer Mode especially without response
41+
// or Interleaved with high ratio. You may need to increase this buffer size
42+
BLEUart bleuart(10*1024);
43+
44+
/* The Image Transfer module sends the image of your choice to Bluefruit LE over UART.
45+
* Each image sent begins with
46+
* - A single byte char '!' (0x21) followed by 'I' helper for image
47+
* - Color depth: 24-bit for RGB 888, 16-bit for RGB 565
48+
* - Image width (uint16 little endian, 2 bytes)
49+
* - Image height (uint16 little endian, 2 bytes)
50+
* - Pixel data encoded as RGB 16/24 bit and suffixed by a single byte CRC.
51+
*
52+
* Format: [ '!' ] [ 'I' ] [uint8_t color bit] [ uint16 width ] [ uint16 height ] [ r g b ] [ r g b ] [ r g b ] … [ CRC ]
53+
*/
54+
55+
uint16_t imageWidth = 0;
56+
uint16_t imageHeight = 0;
57+
uint8_t imageColorBit = 0;
58+
59+
uint32_t totalPixel = 0; // received pixel
60+
61+
// pixel line buffer, should be large enough to hold an image width
62+
uint16_t pixel_buf[512];
63+
64+
// Use software timer to schedule bandwidth negotiation a few seconds after connection
65+
SoftwareTimer negoTimer;
66+
67+
// Statistics for speed testing
68+
uint32_t rxStartTime = 0;
69+
uint32_t rxLastTime = 0;
70+
71+
void setup()
72+
{
73+
Serial.begin(115200);
74+
75+
display.begin();
76+
display.clearBuffer();
77+
78+
#if DISPLAY_IN_USE == EINK_GIZMO
79+
display.setRotation(3);
80+
#else
81+
// todo more eink displays
82+
#endif
83+
84+
// Config the peripheral connection with maximum bandwidth
85+
// more SRAM required by SoftDevice
86+
// Note: All config***() function must be called before begin()
87+
Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);
88+
89+
Bluefruit.begin();
90+
Bluefruit.setTxPower(4); // Check bluefruit.h for supported values
91+
Bluefruit.setName("Bluefruit52");
92+
Bluefruit.Periph.setConnectCallback(connect_callback);
93+
Bluefruit.Periph.setDisconnectCallback(disconnect_callback);
94+
Bluefruit.Periph.setConnInterval(6, 12); // 7.5 - 15 ms
95+
96+
// Configure and Start BLE Uart Service
97+
bleuart.begin();
98+
99+
// Due to huge amount of image data
100+
// NRF52832 doesn't have enough SRAM to queue up received packets using deferred callbacks.
101+
// Therefore it must process data as soon as it comes, this can be done by
102+
// changing the default "deferred" option to false to invoke callback immediately.
103+
// However, the transfer speed will be affected since immediate callback will block BLE task
104+
// to process data especially when tft.drawRGBBitmap() is calling.
105+
#ifdef NRF52840_XXAA
106+
// 2nd argument is true to deferred callbacks i.e queue it up in separated callback Task
107+
bleuart.setRxCallback(bleuart_rx_callback, true);
108+
#else
109+
// 2nd argument is false to invoke callbacks immediately (thus blocking other ble events)
110+
bleuart.setRxCallback(bleuart_rx_callback, false);
111+
#endif
112+
113+
bleuart.setRxOverflowCallback(bleuart_overflow_callback);
114+
115+
// one-shot (non-repeating) 2 seconds timer
116+
negoTimer.begin(2000, negotiate_bandwidth, NULL, false);
117+
118+
// Set up and start advertising
119+
startAdv();
120+
121+
Serial.println("Advertising ... ");
122+
}
123+
124+
void startAdv(void)
125+
{
126+
// Advertising packet
127+
Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
128+
Bluefruit.Advertising.addTxPower();
129+
Bluefruit.Advertising.addAppearance(BLE_APPEARANCE_GENERIC_CLOCK);
130+
131+
// Include bleuart 128-bit uuid
132+
Bluefruit.Advertising.addService(bleuart);
133+
134+
// There is no room for Name in Advertising packet
135+
// Use Scan response for Name
136+
Bluefruit.ScanResponse.addName();
137+
138+
/* Start Advertising
139+
* - Enable auto advertising if disconnected
140+
* - Interval: fast mode = 20 ms, slow mode = 152.5 ms
141+
* - Timeout for fast mode is 30 seconds
142+
* - Start(timeout) with timeout = 0 will advertise forever (until connected)
143+
*
144+
* For recommended advertising interval
145+
* https://developer.apple.com/library/content/qa/qa1931/_index.html
146+
*/
147+
Bluefruit.Advertising.restartOnDisconnect(true);
148+
Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms
149+
Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode
150+
Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds
151+
}
152+
153+
void loop()
154+
{
155+
// nothing to do
156+
}
157+
158+
// Invoked when receiving data from bleuart
159+
// Pull data from bleuart fifo & draw image as soon as possible,
160+
// Otherwise bleuart fifo can be overflowed
161+
void bleuart_rx_callback(uint16_t conn_hdl)
162+
{
163+
(void) conn_hdl;
164+
165+
rxLastTime = millis();
166+
167+
// Received new Image
168+
if ( (imageWidth == 0) && (imageHeight == 0) )
169+
{
170+
// take note of time of first packet
171+
rxStartTime = millis();
172+
173+
// Skip all data until '!I' is found
174+
while( bleuart.available() && bleuart.read() != '!' ) { }
175+
if (bleuart.read() != 'I') return;
176+
177+
if ( !bleuart.available() ) return;
178+
179+
imageColorBit = bleuart.read8();
180+
imageWidth = bleuart.read16();
181+
imageHeight = bleuart.read16();
182+
183+
totalPixel = 0;
184+
185+
display.clearBuffer();
186+
display.fillScreen(EPD_WHITE);
187+
display.setCursor(0, 0);
188+
189+
// Print out the current connection info
190+
BLEConnection* conn = Bluefruit.Connection(conn_hdl);
191+
Serial.printf("Connection Info: PHY = %d Mbps, Conn Interval = %.2f ms, Data Length = %d, MTU = %d\n",
192+
conn->getPHY(), conn->getConnectionInterval()*1.25f, conn->getDataLength(), conn->getMtu());
193+
Serial.printf("Receving an %dx%d Image with %d bit color\n", imageWidth, imageHeight, imageColorBit);
194+
}
195+
196+
// Extract pixel data to buffer and draw image line by line
197+
while ( bleuart.available() >= 3 )
198+
{
199+
uint8_t red, green, blue;
200+
201+
if ( imageColorBit == 24 )
202+
{
203+
// Application send 24-bit color
204+
red = bleuart.read();
205+
green = bleuart.read();
206+
blue = bleuart.read();
207+
}
208+
else if ( imageColorBit == 16 )
209+
{
210+
// Application send 16-bit 565 color
211+
uint16_t c565 = bleuart.read16();
212+
213+
red = (c565 & 0xf800) >> 8;
214+
green = (c565 & 0x07e0) >> 3;
215+
blue = (c565 & 0x001f) << 3;
216+
}
217+
218+
// Convert RGB into Eink Color
219+
uint8_t c = 0;
220+
if ((red < 0x80) && (green < 0x80) && (blue < 0x80)) {
221+
c = EPD_BLACK; // try to infer black
222+
} else if ((red >= 0x80) && (green >= 0x80) && (blue >= 0x80)) {
223+
c = EPD_WHITE;
224+
} else if (red >= 0x80) {
225+
c = EPD_RED; // try to infer red color
226+
}
227+
228+
// store to pixel buffer
229+
pixel_buf[totalPixel % imageWidth] = c;
230+
231+
totalPixel++;
232+
233+
// have enough to draw an image line
234+
if ( (totalPixel % imageWidth) == 0 )
235+
{
236+
display.drawRGBBitmap(0, totalPixel/imageWidth, pixel_buf, imageWidth, 1);
237+
}
238+
}
239+
240+
// all pixel data is received
241+
if ( totalPixel == imageWidth*imageHeight )
242+
{
243+
uint8_t crc = bleuart.read();
244+
(void) crc;
245+
// do checksum later
246+
247+
// print speed summary
248+
print_summary(totalPixel*(imageColorBit/8) + 8, rxLastTime-rxStartTime);
249+
250+
// Display on Eink, will probably take dozens of seconds
251+
Serial.println("Displaying image (~20 seconds) .....");
252+
display.display();
253+
254+
// reset and waiting for new image
255+
imageColorBit = 0;
256+
imageWidth = imageHeight = 0;
257+
totalPixel = 0;
258+
259+
Serial.println("Ready to receive new image");
260+
}
261+
}
262+
263+
void negotiate_bandwidth(TimerHandle_t xTimer)
264+
{
265+
(void) xTimer;
266+
267+
uint16_t conn_hdl = (uint16_t) ((uint32_t) negoTimer.getID());
268+
BLEConnection* conn = Bluefruit.Connection(conn_hdl);
269+
270+
// Switching from 1 Mb to 2 Mb PHY if needed
271+
if ( conn->connected() )
272+
{
273+
// Requesting to Switching to 2MB PHY, larger data length and bigger MTU
274+
// will increase the throughput on supported central. This should already
275+
// be done with latest Bluefruit app.
276+
//
277+
// However, some Android devices require 2Mb PHY switching must be initiated
278+
// from nRF side
279+
if ( conn->getPHY() == BLE_GAP_PHY_1MBPS )
280+
{
281+
Serial.println("Requesting PHY change from 1 Mb to 2Mb");
282+
conn->requestPHY();
283+
284+
// Data Length and MTU should already be done by Bluefruit app
285+
// conn->requestDataLengthUpdate();
286+
// conn->requestMtuExchange(247);
287+
}
288+
}
289+
}
290+
291+
void connect_callback(uint16_t conn_handle)
292+
{
293+
// Set connection handle as timer ID
294+
// Then schedule negotiation after a few seconds, we should not
295+
// negotiate here since it increases chance to conflict with other
296+
// on-going negotiation from central after connection
297+
negoTimer.setID((void*) conn_handle);
298+
negoTimer.start();
299+
300+
Serial.println("Connected");
301+
Serial.println("Ready to receive new image");
302+
}
303+
304+
void print_summary(uint32_t count, uint32_t ms)
305+
{
306+
float sec = ms / 1000.0F;
307+
308+
Serial.printf("Received %d bytes in %.2f seconds\n", count, sec);
309+
Serial.printf("Speed: %.2f KB/s\n\n", (count / 1024.0F) / sec);
310+
}
311+
312+
void bleuart_overflow_callback(uint16_t conn_hdl, uint16_t leftover)
313+
{
314+
(void) conn_hdl;
315+
(void) leftover;
316+
317+
Serial.println("BLEUART rx buffer OVERFLOWED!");
318+
Serial.println("Please increase buffer size for bleuart");
319+
}
320+
321+
/**
322+
* invoked when a connection is dropped
323+
* @param conn_handle connection where this event happens
324+
* @param reason is a BLE_HCI_STATUS_CODE which can be found in ble_hci.h
325+
*/
326+
void disconnect_callback(uint16_t conn_handle, uint8_t reason)
327+
{
328+
(void) conn_handle;
329+
(void) reason;
330+
331+
imageColorBit = 0;
332+
imageWidth = imageHeight = 0;
333+
totalPixel = 0;
334+
335+
bleuart.flush();
336+
}

0 commit comments

Comments
 (0)