This is about usage of TFT displays, and at the time of writing, Hackster provides more than 700 articles using TFT displays. This one is especially for educational puspose.
Part 1: 256 colorsThere is a zoo of different graphic color formats but one of the most simple ones ist the BMP format created by Microsoft. The cheap TFT displays often cannot show more than 65536 colors, but when you want to store the image in an ARDUINO UNO offering only 32000 bytes you have to go for the 8-bit format with only 256 colors. The simple 1.8" displays offer 128x160 pixels resulting in 21558 bytes (including the file header) which fits perfectly in the FLASH memory of an ATmega328.
Hardware: there is not much to say about: the simple 1.8"-TFT displays mostly have just 8 pins, and they have to be connected to pin-8 to pin-13 (12 excluded) plus the power pins (LED to 3.3 volts).
The TFT library shipped with the ARDUINO IDE can be found in RETIRED:
It offers four modes of orientation. Images are not presented instantly but drawing this amount of data takes some time while you can watch how the drawing takes place. Data inside a BMP file are stored "bottom-up", but when you want to draw them "top-down" you need to read the file in reverse mode. All this results in eight modes of presentation, and in this sketch, one of these modes is chosen at random whenever you press the RESET button.
To prepare the image you want to show you need to downscale it to 128x160 pixels and store it uncompressed with only 256 colors. To enable the ARDUINO IDE to access the data, it has to be stored as an array of bytes. You can use HxD or any other Hex editor to convert the data. Make sure you modify the extension to "*.h". and add the term "const PROGMEM" to the array declaration.
You can store even two bitmaps in the FLASH memory if you reduce their size to 92x115 pixels but that is the very limit of Arduino-UNO. An example is given in the attachments.
Part 2: Monochrome GraphicsFrom the very beginning, ARDUINO provided a library called TFT.h which also included a function named drawBitmap to display black-and-white graphics.
This function only supports monochrome graphics that can be stored in the Microsoft BMP file format. But when you use this function, the picture will be displayed upside-down as Microsoft placed (0/0) at the lower left while on displays (0/0) is located on the upper left corner.
As Arduino never updated this library I had to code the function myself:
void bitMap(byte x, byte y, const byte data[]) {
int ofs = data[10];
int w = data[18];
int h = data[22];
boolean k = data[46] == 0;
word bw = (w + 7) / 8;
for (byte row = 0; row < h; row++) {
byte x0 = x;
word bc = ofs + row * bw;
byte c, m = 0;
for (byte col = 0; col < w; col++ ) {
if (m == 0) {
m = B10000000;
c = pgm_read_byte(data + bc++);
}
boolean d = (c & m) == 0;
if (d ^ k) tft.drawPixel(x0, y + h - row, ST7735_WHITE);
x0++;
m = m >> 1;
}
}
}
To use this function you have shrink your graphics to 128x160 pixels or less, convert it to an array of bytes (I prefer the Hexeditor HxD), rename it to *.c.
Do not remove the header bytes of this file, as the are needed to determine width and height. Include that file into your sketch, and call the function like
bitMap(0, 0, rawData);
As a monochrome picture only uses 2600 bytes you can store many of them and display them at random. Drawing one picture will take about two seconds.
Part 3: Rainbow - convert HUE to RGBThe standard TFT library does not offer such a function. There are algorithms which provide means to control saturation and brightness. If you do not need it you can use the little functions included in this sketch:
#include <TFT.h>
const byte cs = 10;
const byte dc = 9;
const byte rst = 8;
TFT tft = TFT(cs, dc, rst);
int N = 160; // = width of TFT
int k = 127;
// --------------------------------------------
float N6 = N / 6;
float m = k / N6;
int A = 2 * N6;
int B = 3 * N6;
int C = 4 * N6;
word hue2RGB_Lin(int hue) {
int r = max(abs(m * (hue - B)) - 128, 0);
int g = max(255 - abs(m * (hue - A)), 0);
int b = max(255 - abs(m * (hue - C)), 0);
return tft.Color565(r, g, b);
}
// --------------------------------------------
word hue2RGB_Trig(int hue) {
float hue3f = hue * 3.0 / N;
int hue3 = hue3f;
float x = (hue3f - hue3) * PI;
float c = cos(x) * k;
switch (hue3) {
case 0: return tft.Color565(k + c, k - c, 0);
case 1: return tft.Color565(0, k + c, k - c);
case 2: return tft.Color565(k - c, 0, k + c);
}
}
// --------------------------------------------
void setup() {
Serial.begin(9600);
Serial.println(__FILE__);
tft.begin();
tft.setRotation(3);
for (int i = 0; i < N; i++) {
setzeNeueFarbe(i, i, 0);
setzeNeueFarbe(i, i, 64);
}
tft.setTextSize(2);
tft.setTextColor(0);
tft.setCursor(4, 28);
tft.print("linear");
tft.setCursor(4, 92);
tft.print("trigonometric");
}
void loop() {}
void setzeNeueFarbe(int i, int hue, int x) {
word color;
if (x == 0)
color = hue2RGB_Lin(hue);
else
color = hue2RGB_Trig(hue);
tft.drawFastVLine(i, x, 63, color);
}
The functions hue2RGB_Lin and hue2RGB_Trig produce slightly different results:
Just select which one you prefer.
Part 4: Improve color format conversionThe function Color565 to convert RGB888 (24 bits) to RGB565 (16 bits) uses alot of bit-shifts in loops. This code does it much faster:
word tftColor565(byte r, byte g, byte b) {
word color;
// calculate high byte:
// GREEN, 3 higher bits:
byte gh = g;
gh = gh >> 1;
gh = gh >> 1;
gh = gh >> 1;
gh = gh >> 1;
gh = gh >> 1;
// gcc makes: swap + lsr
// high byte from RED:
((byte*)(&color))[1] = (r & B11111000) | gh;
// calculate low byte:
// GREEN, 3 lower bits:
byte gl = g & B00011100;
gl = gl << 1;
gl = gl << 1;
gl = gl << 1;
// BLUE:
b = b >> 1;
b = b >> 1;
b = b >> 1;
((byte*)(&color))[0] = gl | b;
// end of RGB888 to RGB565
return color;
}
It speeds up the conversion by a factor of about 3.
Part 5: Rotating Geometric ObjectsEveryone is rotating cubes, why not using somewhat different?
Here we go:
#include <TFT.h>
byte cs = 10;
byte dc = 9;
byte rst = 8;
TFT tft = TFT(cs, dc, rst);
int w, h, mx, my, w4, h4;
// Ikosaeder:
// corners, Ecken:
const int NE = 12;
// areas, Flaechen:
const int NF = 20;
const float PHI = (sqrt(5) + 1) / 2;
struct POINT {
float x, y, z;
}
// do not change the sequence of this entries
// diese Reihenfolge NIEMALS aendern!!!
ecke[NE] = {
{0, +1, +PHI},
{0, +1, -PHI},
{0, -1, +PHI},
{0, -1, -PHI},
{ +1, +PHI, 0},
{ +1, -PHI, 0},
{ -1, +PHI, 0},
{ -1, -PHI, 0},
{ +PHI, 0, +1},
{ +PHI, 0, -1},
{ -PHI, 0, +1},
{ -PHI, 0, -1}
};
struct TRIANGLE {
// Nummer der Ecke, number of the corner
int a, b, c;
// z-Koodinate des Schwerpunkts, center of mass
float sz;
} tri[NF] = {
{0, 2, 8}, // ok
{0, 2, 10}, // ok
{0, 4, 6}, // ok
{0, 4, 8}, // ok
{0, 6, 10}, // ok - 0 4 6
{1, 3, 9}, // ok
{1, 3, 11}, // ok
{1, 4, 6}, // ok
{1, 4, 9}, // ok
{1, 6, 11}, // ok
{2, 5, 7}, // ok
{2, 5, 8}, // ok - 0 2 8
{2, 7, 10}, // ok - 2 5 7
{3, 5, 7}, // ok - 2 7 5
{3, 5, 9}, // ok - 1 3 9
{3, 7, 11}, // ok - 3 5 7, 1 3 11
{4, 8, 9}, // ok - 0 4 8
{5, 8, 9}, // ok - 3 5 9
{6, 10, 11}, // ok - 0 6 10
{7, 10, 11} // ok - 3 7 11
};
void setup() {
Serial.begin(9600);
Serial.println(__FILE__);
tft.begin();
tft.setRotation(0);
w = tft.width();
h = tft.height();
mx = w / 2;
my = h / 2;
w4 = w / 4;
h4 = w / 4;
}
void loop() {
tft.fillScreen(ST7735_BLACK);
zeichneDreiecke();
delay(500);
dreheKoerper();
}
void zeichneDreiecke() {
for (int i = 0; i < NF; i++) {
TRIANGLE t = tri[i];
float az = ecke[t.a].z;
float bz = ecke[t.b].z;
float cz = ecke[t.c].z;
float sz = (az + bz + cz) / 3;
if (sz < 0) continue;
int x1 = mx + w4 * ecke[t.a].x;
int y1 = my + h4 * ecke[t.a].y;
int x2 = mx + w4 * ecke[t.b].x;
int y2 = my + h4 * ecke[t.b].y;
int x3 = mx + w4 * ecke[t.c].x;
int y3 = my + h4 * ecke[t.c].y;
tft.fillTriangle(x1, y1, x2, y2, x3, y3, hue(i));
tft.drawTriangle(x1, y1, x2, y2, x3, y3, ST7735_WHITE);
}
}
float alpha;
float beta;
void dreheKoerper() {
// rotation around x-axis Drehung um x-Achse
alpha = alpha + 0.005;
// rotation around z-axis Drehung um z-Achse
beta = beta + 0.01;
float sa = sin(alpha);
float sb = sin(beta);
float ca = cos(alpha);
float cb = cos(beta);
for (int i = 0; i < NE; i++) {
POINT p = ecke[i];
POINT q;
q.x = p.x * cb + p.z * sb;
q.y = -p.x * sa * sb + p.y * ca + p.z * sa * cb;
q.z = -p.x * ca * sb - p.y * sa + p.z * ca * cb;
ecke[i] = q;
}
}
word hue(byte x) {
int M2 = 255;
int M1 = M2 / 2;
float m = 3 * M2 / 20;
float G = m * x - 2 * M1;
float R = m * x - 3 * M1;
float B = m * x - 4 * M1;
byte r = max(-M1 + abs(R), 0);
byte g = max(+M2 - abs(G), 0);
byte b = max(+M2 - abs(B), 0);
return tft.Color565(r, g, b);
}
Have fun!
Part 6: Time to fill Rectangles, Triangles, CirclesThe standard library offers functions to fill those objects with a certain color. In order to do that, each pixel has to be set to that color, but the function has to determine the range of pixels. With rectangles it is very easy but triangles and circles require more steps to do that. This is to compare the time needed to fill these objects: The sizes were set to get the same amount of pixels to be colored.
The algorithm for the benchmark is given below.
/*
Vergleiche die Zeit zum Fuellen verschieden-
foermiger, aber gleich grosser Objekte:
Dreieck : 76 ms
Rechteck: 58 ms
Kreis : 81 ms
*/
#include <TFT.h>
byte cs = 10;
byte dc = 9;
byte rst = 8;
TFT tft = TFT(cs, dc, rst);
int w, h;
struct PUNKT {
int x, y;
char ch;
} A, B, C;
void setup() {
tft.begin();
// quer
tft.setRotation(1);
// losche alles:
w = tft.width();
h = tft.height();
benchmark("Dreieck", dreieck);
benchmark("Rechteck", rechteck);
benchmark("Kreis", kreis);
}
void loop() {}
void benchmark(const char *c, int (*function)()) {
tft.background(ST7735_RED);
long t1 = millis();
int v = (*function)();
long t2 = millis();
tft.setCursor(0, 0);
tft.println(strrchr(__FILE__, '\\') + 1);
tft.println(__TIME__);
tft.println(c);
tft.setCursor(0, 45);
tft.print("Flaeche = ");
tft.print(v);
tft.println(" pixel");
tft.print("Zeit = ");
tft.print(t2 - t1);
tft.println(" ms");
delay(5000);
}
int dreieck() {
A = {w / 2, h, 'A'};
B = {w, 0, 'B'};
C = {0, h / 2, 'C'};
tft.fillTriangle(A.x, A.y,
B.x, B.y,
C.x, C.y,
ST7735_BLUE);
beschrifte(A);
beschrifte(B);
beschrifte(C);
return area(A, B, C);
}
void beschrifte(PUNKT P) {
TIMSK0 = 0; // halte die Uhr an
tft.setCursor(min(P.x, w - 10),
min(P.y, h - 10));
tft.print(P.ch);
TIMSK0 = 1; // Uhr laeuft weiter
}
int area(PUNKT p1, PUNKT p2, PUNKT p3) {
float sa = sqrt(sq(p2.x - p3.x) + sq(p2.y - p3.y));
float sb = sqrt(sq(p1.x - p3.x) + sq(p1.y - p3.y));
float sc = sqrt(sq(p2.x - p1.x) + sq(p2.y - p1.y));
float s = (sa + sb + sc) / 2;
return sqrt(s * (s - sa) * (s - sb) * (s - sc));
}
int rechteck() {
int a = 128;
int b = 60;
PUNKT A = {(w - a) / 2, (h - b) / 2, 'A'};
PUNKT B = {A.x + a, A.y, 'B'};
PUNKT C = {A.x + a, A.y + b, 'C'};
PUNKT D = {A.x, A.y + b, 'D'};
tft.fillRect(A.x, A.y, a, b, ST7735_BLUE);
beschrifte(A);
beschrifte(B);
beschrifte(C);
beschrifte(D);
return a * b;
}
int kreis() {
int r = 49;
tft.fillCircle(w / 2, h / 2, 49, ST7735_BLUE);
PUNKT p = {w / 2, h / 2, 'M'};
beschrifte(p);
return sq(r) * PI;
}
Comments
Please log in or sign up to comment.