Peripheral mode (UART)
What this example shows
Use Inkplate as a display for another device. With this sketch you do not write any drawing code: you send short text commands over the serial port, and Inkplate draws shapes and text, shows images from the microSD card and reports the battery voltage. Because the commands are plain text over UART, any platform that can write to a serial port can control the screen.
The sketch's comments state the settings: 115200 baud, standard parity, each command ending with both \n and \r. You can send the commands over the USB port, or by connecting directly to the ESP32's TX and RX pins. The sketch also says that, because UART is slow, drawing many single pixels is not recommended; load bitmaps and pictures onto the microSD card and draw them from there instead. Every Inkplate comes with this mode from the factory.
Command format
Each command is #, a command character, the arguments in round brackets, and * at the end, for example #8(10,10,100,50,0)*. Numbers are written in decimal and separated by commas. Nothing is shown on the screen until you send #L(1)* after the drawing commands, which is the same as calling display().
| Command | Arguments | Function it calls |
|---|---|---|
#?(…)* | none | Replies OK |
#0(x,y,c)* | position, color | drawPixel |
#1(x1,y1,x2,y2,c)* | two end points, color | drawLine |
#2(x,y,l,c)* | start, length, color | drawFastVLine |
#3(x,y,l,c)* | start, length, color | drawFastHLine |
#4(x,y,w,h,c)* | top-left corner, size, color | drawRect |
#5(x,y,r,c)* | center, radius, color | drawCircle |
#6(x1,y1,x2,y2,x3,y3,c)* | three corners, color | drawTriangle |
#7(x,y,w,h,r,c)* | top-left corner, size, corner radius, color | drawRoundRect |
#8(x,y,w,h,c)* | top-left corner, size, color | fillRect |
#9(x,y,r,c)* | center, radius, color | fillCircle |
#A(x1,y1,x2,y2,x3,y3,c)* | three corners, color | fillTriangle |
#B(x,y,w,h,r,c)* | top-left corner, size, corner radius, color | fillRoundRect |
#C("hex")* | text as hexadecimal byte pairs in quotes | print |
#D(s)* | text size | setTextSize |
#E(x,y)* | cursor position | setCursor |
#F(T)* or #F(F)* | T for wrapping on, F for off | setTextWrap |
#G(r)* | rotation, only the lowest two bits are used | setRotation |
#H(x,y,"hex")* | position, file name as hexadecimal byte pairs in quotes | image.drawBitmapFromSd |
#K(1)* | 1 | clearDisplay |
#L(1)* | 1 | display |
#M(a,b,c)* | three numbers that are read and not used | display |
#P(?)* | ? | Replies #P( battery voltage )* from readBattery |
#S(x,y,"hex")* | position, file name as hexadecimal byte pairs in quotes | image.draw |
#T(x1,y1,x2,y2,c,t)* | two end points, color, thickness | drawThickLine |
#U(rx,ry,xc,yc,c)* | radii, center, color | drawElipse |
#V(rx,ry,xc,yc,c)* | radii, center, color | fillElipse |
Text and file names are sent as hexadecimal pairs: each pair of digits becomes one character, so Hi is 4869. For #H and #S the sketch initializes the microSD card, draws the file, and answers #H( followed by the result and )*; when the card cannot be initialized it answers #H(-1)*. The #S command also answers with #H(, as the code writes it.
Example code
#include <Inkplate.h>
Inkplate inkplate;
#define BUFFER_SIZE 1000
char commandBuffer[BUFFER_SIZE + 1];
char strTemp[2001];
void setup()
{
inkplate.begin();
Serial.begin(115200);
memset(commandBuffer, 0, BUFFER_SIZE);
}
void loop()
{
// put your main code here, to run repeatedly:
if (Serial.available())
{
while (Serial.available())
{
for (int i = 0; i < (BUFFER_SIZE - 1); i++)
{
commandBuffer[i] = commandBuffer[i + 1];
}
commandBuffer[BUFFER_SIZE - 1] = Serial.read();
}
}
char *s = NULL;
char *e = NULL;
for (int i = 0; i < BUFFER_SIZE; i++)
{
if (commandBuffer[i] == '#' && s == NULL)
s = &commandBuffer[i];
if (commandBuffer[i] == '*' && e == NULL)
e = &commandBuffer[i];
}
if (s != NULL && e != NULL)
{
if ((e - s) > 0)
{
int x, x1, x2, y, y1, y2, x3, y3, l, c, w, h, r, n, rx, ry, xc, yc;
char b;
char temp[150];
switch (*(s + 1))
{
case '?':
Serial.print("OK");
break;
case '0':
sscanf(s + 3, "%d,%d,%d", &x, &y, &c);
inkplate.drawPixel(x, y, c);
break;
case '1':
sscanf(s + 3, "%d,%d,%d,%d,%d", &x1, &y1, &x2, &y2, &c);
inkplate.drawLine(x1, y1, x2, y2, c);
break;
case '2':
sscanf(s + 3, "%d,%d,%d,%d", &x, &y, &l, &c);
inkplate.drawFastVLine(x, y, l, c);
break;
case '3':
sscanf(s + 3, "%d,%d,%d,%d", &x, &y, &l, &c);
inkplate.drawFastHLine(x, y, l, c);
break;
case '4':
sscanf(s + 3, "%d,%d,%d,%d,%d", &x, &y, &w, &h, &c);
inkplate.drawRect(x, y, w, h, c);
break;
case '5':
sscanf(s + 3, "%d,%d,%d,%d", &x, &y, &r, &c);
inkplate.drawCircle(x, y, r, c);
break;
case '6':
sscanf(s + 3, "%d,%d,%d,%d,%d,%d,%d", &x1, &y1, &x2, &y2, &x3, &y3, &c);
inkplate.drawTriangle(x1, y1, x2, y2, x3, y3, c);
break;
case '7':
sscanf(s + 3, "%d,%d,%d,%d,%d,%d", &x, &y, &w, &h, &r, &c);
inkplate.drawRoundRect(x, y, w, h, r, c);
break;
case '8':
sscanf(s + 3, "%d,%d,%d,%d,%d", &x, &y, &w, &h, &c);
inkplate.fillRect(x, y, w, h, c);
break;
case '9':
sscanf(s + 3, "%d,%d,%d,%d", &x, &y, &r, &c);
inkplate.fillCircle(x, y, r, c);
break;
case 'A':
sscanf(s + 3, "%d,%d,%d,%d,%d,%d,%d", &x1, &y1, &x2, &y2, &x3, &y3, &c);
inkplate.fillTriangle(x1, y1, x2, y2, x3, y3, c);
break;
case 'B':
sscanf(s + 3, "%d,%d,%d,%d,%d,%d", &x, &y, &w, &h, &r, &c);
inkplate.fillRoundRect(x, y, w, h, r, c);
break;
case 'C':
sscanf(s + 3, "\"%2000[^\"]\"", strTemp);
n = strlen(strTemp);
for (int i = 0; i < n; i++)
{
strTemp[i] = toupper(strTemp[i]);
}
for (int i = 0; i < n; i += 2)
{
strTemp[i / 2] = (hexToChar(strTemp[i]) << 4) | (hexToChar(strTemp[i + 1]) & 0x0F);
}
strTemp[n / 2] = 0;
inkplate.print(strTemp);
break;
case 'D':
sscanf(s + 3, "%d", &c);
inkplate.setTextSize(c);
break;
case 'E':
sscanf(s + 3, "%d,%d", &x, &y);
inkplate.setCursor(x, y);
break;
case 'F':
sscanf(s + 3, "%c", &b);
if (b == 'T')
inkplate.setTextWrap(true);
if (b == 'F')
inkplate.setTextWrap(false);
break;
case 'G':
sscanf(s + 3, "%d", &c);
c &= 3;
inkplate.setRotation(c);
break;
case 'H':
sscanf(s + 3, "%d,%d,\"%149[^\"]\"", &x, &y, strTemp);
n = strlen(strTemp);
for (int i = 0; i < n; i++)
{
strTemp[i] = toupper(strTemp[i]);
}
for (int i = 0; i < n; i += 2)
{
strTemp[i / 2] = (hexToChar(strTemp[i]) << 4) | (hexToChar(strTemp[i + 1]) & 0x0F);
}
strTemp[n / 2] = 0;
r = inkplate.sdCardInit();
if (r)
{
r = inkplate.image.drawBitmapFromSd(strTemp, x, y);
Serial.print("#H(");
Serial.print(r, DEC);
Serial.println(")*");
Serial.flush();
}
else
{
Serial.println("#H(-1)*");
Serial.flush();
}
break;
case 'K':
sscanf(s + 3, "%c", &b);
if (b == '1')
{
inkplate.clearDisplay();
}
break;
case 'L':
sscanf(s + 3, "%c", &b);
if (b == '1')
{
inkplate.display();
}
break;
case 'M':
sscanf(s + 3, "%d,%d,%d", &y1, &x2, &y2);
inkplate.display();
break;
case 'P':
sscanf(s + 3, "%c", &b);
if (b == '?')
{
Serial.print("#P(");
Serial.print(inkplate.readBattery(), 2);
Serial.println(")*");
Serial.flush();
}
break;
case 'S':
sscanf(s + 3, "%d,%d,\"%149[^\"]\"", &x, &y, strTemp);
n = strlen(strTemp);
for (int i = 0; i < n; i++)
{
strTemp[i] = toupper(strTemp[i]);
}
for (int i = 0; i < n; i += 2)
{
strTemp[i / 2] = (hexToChar(strTemp[i]) << 4) | (hexToChar(strTemp[i + 1]) & 0x0F);
}
strTemp[n / 2] = 0;
r = inkplate.sdCardInit();
if (r)
{
r = inkplate.image.draw(strTemp, x, y);
Serial.print("#H(");
Serial.print(r, DEC);
Serial.println(")*");
Serial.flush();
}
else
{
Serial.println("#H(-1)*");
Serial.flush();
}
break;
case 'T':
int t;
sscanf(s + 3, "%d,%d,%d,%d,%d,%d", &x1, &y1, &x2, &y2, &c, &t);
inkplate.drawThickLine(x1, y1, x2, y2, c, t);
break;
case 'U':
sscanf(s + 3, "%d,%d,%d,%d,%d", &rx, &ry, &xc, &yc, &c);
inkplate.drawElipse(rx, ry, xc, yc, c);
break;
case 'V':
sscanf(s + 3, "%d,%d,%d,%d,%d", &rx, &ry, &xc, &yc, &c);
inkplate.fillElipse(rx, ry, xc, yc, c);
break;
}
*s = 0;
*e = 0;
}
}
}
int hexToChar(char c)
{
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
return -1;
}
Functions used
Inkplate()returns NoneCreates the Inkplate object. Inkplate 7SPECTRA has a single six-color mode, so the constructor takes no mode argument.
ReturnsConstructor; no return value.
inkplate.begin()returns voidInitializes the library, the peripherals and the frame buffer. Call it once in setup().
ReturnsNothing.
inkplate.drawPixel(int16_t x, int16_t y, uint16_t color)returns voidSets one pixel in the frame buffer. Command 0.
ReturnsNothing.
inkplate.drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint16_t color)returns voidDraws a line between two points. Command 1. From the Adafruit GFX class the library builds on.
ReturnsNothing.
inkplate.drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color)returns voidDraws a vertical line of the given length. Command 2.
ReturnsNothing.
inkplate.drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color)returns voidDraws a horizontal line of the given length. Command 3.
ReturnsNothing.
inkplate.drawRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color)returns voidDraws the outline of a rectangle. Command 4.
ReturnsNothing.
inkplate.drawCircle(int16_t x0, int16_t y0, int16_t r, uint16_t color)returns voidDraws the outline of a circle. Command 5.
ReturnsNothing.
inkplate.drawTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, int16_t y2, uint16_t color)returns voidDraws the outline of a triangle. Command 6.
ReturnsNothing.
inkplate.drawRoundRect(int16_t x, int16_t y, int16_t w, int16_t h, int16_t r, uint16_t color)returns voidDraws the outline of a rectangle with rounded corners. Command 7.
ReturnsNothing.
inkplate.fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color)returns voidDraws a filled rectangle with its top-left corner at (x, y). Command 8.
ReturnsNothing.
inkplate.fillCircle(int16_t x0, int16_t y0, int16_t r, uint16_t color)returns voidDraws a filled circle. Command 9.
ReturnsNothing.
inkplate.fillTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, int16_t y2, uint16_t color)returns voidDraws a filled triangle. Command A.
ReturnsNothing.
inkplate.fillRoundRect(int16_t x, int16_t y, int16_t w, int16_t h, int16_t r, uint16_t color)returns voidDraws a filled rectangle with rounded corners. Command B.
ReturnsNothing.
inkplate.print(text)returns size_tPrints the decoded text into the frame buffer at the cursor, like Serial.print(). Command C.
ReturnsNumber of bytes printed.
inkplate.setTextSize(uint8_t s)returns voidSets the text magnification. Command D.
ReturnsNothing.
inkplate.setCursor(int16_t x, int16_t y)returns voidSets where the next printed text starts. Command E.
ReturnsNothing.
inkplate.setTextWrap(bool w)returns voidChooses whether text that does not fit on a line continues on the next line. Command F.
ReturnsNothing.
inkplate.setRotation(uint8_t r)returns voidSets the screen rotation. The sketch passes only the lowest two bits of the number it receives. Command G.
ReturnsNothing.
inkplate.sdCardInit()returns int16_tSwitches on the microSD card's supply, starts its SPI bus and mounts the card. Commands H and S call it before they read a file.
Returns1 if the card was mounted, 0 if it was not.
inkplate.image.drawBitmapFromSd(const char *fileName, int x, int y, bool dither = 0, bool invert = 0)returns boolOpens a bitmap file on the microSD card by name and draws it into the frame buffer with its top-left corner at (x, y). Command H. The sketch sends the result back, 1 on success.
Returnstrue if the bitmap was drawn.
inkplate.image.draw(const char *path, int x, int y, bool dither = 1, bool invert = 0)returns boolDraws an image file from the microSD card at x, y; the library picks the decoder from the file extension (bmp, dib, jpg, jpeg or png). Command S.
Returnstrue if the image was drawn.
inkplate.clearDisplay()returns voidFills the frame buffer with white. It does not change the panel. Command K.
ReturnsNothing.
inkplate.display(bool _leaveOn = 0)returns voidSends the whole frame buffer to the panel and refreshes it. Commands L and M.
ReturnsNothing.
inkplate.readBattery()returns doubleMeasures the battery voltage. The sketch sends it back with two decimals. Command P.
ReturnsThe battery voltage in volts.
inkplate.drawThickLine(int x1, int y1, int x2, int y2, int color, float thickness)returns voidDraws a line of the given thickness between two points. Command T.
ReturnsNothing.
inkplate.drawElipse(int rx, int ry, int xc, int yc, int c)returns voidDraws the outline of an ellipse with radii rx and ry around the center (xc, yc). The name is spelled this way in the library. Command U.
ReturnsNothing.
inkplate.fillElipse(int rx, int ry, int xc, int yc, int c)returns voidDraws a filled ellipse with radii rx and ry around the center (xc, yc). Command V.
ReturnsNothing.
Serial.available()returns intArduino core: the number of bytes waiting in the serial receive buffer. The sketch reads them all into its command buffer.
ReturnsNumber of bytes.
Serial.read()returns intArduino core: reads one byte from the serial port.
ReturnsThe byte, or -1 if none is available.
Serial.print(value, format)returns size_tArduino core: writes a value to the serial port. The sketch uses it for the replies. DEC prints a number in decimal.
ReturnsNumber of bytes written.
Serial.println(value)returns size_tArduino core: like Serial.print(), followed by a line break.
ReturnsNumber of bytes written.
Serial.flush()returns voidArduino core: waits until everything written has been sent. The sketch calls it after each reply.
ReturnsNothing.
What to expect
After upload the screen stays as it is until you send commands. Send #?(…)* and Inkplate answers OK. Send drawing commands and then #L(1)*, and the screen refreshes with the result. Send #P(?)* and it answers #P( and the battery voltage with two decimals, then )*.
🔗Open the complete example
examples/Inkplate7SPECTRA/Diagnostics/Inkplate7SPECTRA_Peripheral_Mode