STM32NUCLEO – pierwsze kroki w środowisku mbed.org – obsługa graficznego LCD

Zastosowany w przykładzie moduł KAmodTFT2 zawiera matrycę LCD RGB o wymiarach 132×132 piksele oraz kontroler PCF8833, którego linie interfejsu szeregowego zostały wyprowadzone na złącze goldpinowe:

  • linia CLK – sygnał zegarowy taktujący dane na linii DIN,
  • linia DIN – sygnał danych wejściowych synchronizowany sygnałem CLK,
  • linia RES – sygnał zerowania (stan niski jest stanem aktywnym),
  • linia SCE – sygnał aktywujący interfejs szeregowy (stan niski jest stanem aktywnym).

Moduł KAmodTFT2 może być zasilany napięciem 3.3V lub 5V, które można wybrać przy pomocy zworki JP1. W danym przykładzie napięcie to wynosi 3.3V i jest dostarczane przez zestaw Nucleo-F334R8. Schemat podłączenia linii modułu KAmodTFT2 do zestawu przedstawia rysunek 1.

 

Rys. 1. Schemat podłączenia modułu KAmodTFT2 do zestawu Nucleo-F334R8

Komunikacja pomiędzy mikrokontrolerem zamontowanym na płytce Nucleo a kontrolerem PCF8833 modułu KAmodTFT2 odbywa się z wykorzystaniem protokołu SPI. Ponieważ jednak długość słowa danych w pojedynczej ramce wynosi 9, a nie standardowo 8 lub 16 bitów (rysunek 2) to w niektórych mikrokontrolerach może być niemożliwym wykorzystanie sprzętowego interfejsu SPI i w tej sytuacji pozostaje jedynie programowa jego realizacja. Na szczęście zgodnie z dokumentacją STM32F334xx reference manual mikrokontroler STM32F334R8T6 ma możliwość konfiguracji długości słowa danych od 4 do 16 bitów.

 

Rys. 2. Przykładowy przebieg sygnałów na magistrali SPI podczas komunikacji z kontrolerem PCF8833 [1]

Sterownik wyświetlacza

W internetowym środowisku programistycznym mbed można znaleźć co najmniej dwa sterowniki wyświetlacza z kontrolerem PCF8833, między innymi ten, który zawarty jest w bibliotece NokiaLCD (rysunek 3). Jednakże mogą one nie współpracować poprawnie ze sprzętowym interfejsem SPI mikrokontrolera w zakresie konfiguracji 9-bitowej długości słowa danych (co jakiś czas biblioteki w mbed są poprawiane i uzupełniane, dlatego warto na początku zapoznać się z ich opisem oraz przejrzeć stosowne tematy na forum dyskusyjnym http://mbed.org/forum/). Z tego powodu bardziej sensownym (i pewnym) rozwiązaniem byłoby samodzielne przygotowanie takiego sterownika, zapoznając się przy okazji z możliwościami jakie daje programowanie w języku C++ (dotyczy to m.in. osób, które dobrze znają język C i chcą rozpocząć programowanie w C++).

 

Rys. 3. Biblioteka NokiaLCD jest dostępna w pakiecie mbed

Pierwszym krokiem w procesie przygotowania własnego sterownika wyświetlacza jest stworzenie nowego projektu lub zaimportowanie już istniejącego – zostało to opisane w poprzedniej części artykułu. Następnie należy utworzyć pliki implementacyjne (.cpp) i nagłówkowe (.h) o nazwach Graphics oraz NokiaLCD (rysunek 4). Dodatkowo będzie potrzebny jeszcze jeden plik o nazwie Fonts.c przechowujący deklaracje tablic z wzorcami znaków dla różnych czcionek.  Kody źródłowe oraz informacje zawarte na stronach [2, 3, 4] mogą posłużyć jako podstawa do opracowania sterownika w języku C++.

 

Rys. 4. Pliki projektu o nazwie Nucleo-F334R8_NokiaLCD

W pliku nagłówkowym NokiaLCD.h znajdują się definicje stałych (komend kontrolera PCF8833 i kolorów w formacie RGB565) oraz definicja klasy NokiaLCD. Jako prywatne składowe klasy deklarujemy te pola i metody, które będą wykorzystywane tylko w jej obrębie i które nie powinny być widoczne dla użytkownika tejże klasy (tymi składowymi są m.in. zmienne typu DigitalOut przechowujące numer linii GPIO oraz funkcje z prefiksem „spi_” przeznaczone do pracy z magistralą SPI).

#define LCD_RGB565_BLACK 0x0000
#define LCD_RGB565_BLUE 0x001F
#define LCD_RGB565_GRAY 0x8430
#define LCD_RGB565_GREEN 0x07E0
#define LCD_RGB565_ORANGE 0xFDC5
#define LCD_RGB565_RED 0xF800
#define LCD_RGB565_WHITE 0xFFFF
#define LCD_RGB565_YELLOW 0xFFF8

#include “Graphics.h”

class NokiaLCD {
private:
DigitalOut CLK, CS, DATA, RES;

unsigned int fg_color;
unsigned int text_bg_color, text_fg_color;
int text_x, text_y;
int text_x_last, text_y_last;
int text_size;

// SPI-related functions
void spi_write(unsigned char data);
void spi_writeCommand(unsigned char cmd);
void spi_writeData(unsigned char data);

public:
NokiaLCD(PinName clk, PinName cs, PinName data, PinName res);

// Control functions
void clear(unsigned int color);
void setContrast(char contrast);

// Graphics-related functions
//[…] void drawChar(char c, int x, int y, int size, unsigned int fgColor, unsigned int bgColor);
void drawPixel(int x, int y, unsigned int color);
void fillRect(int x, int y, int width, int height, unsigned int color);
void setFGColor(unsigned int color);

// Text-related functions
//[…] void gotoXY(int x, int y);
void setTextBGColor(unsigned int color);
void setTextFGColor(unsigned int color);
void setTextSize(int size);
void writeChar(char c);
void writeCharAtXY(char c, int x, int y);
void writeString(char *s);
void writeStringAtXY(char *s, int x, int y);
};

W odpowiednim pliku implementacyjnym NokiaLCD.cpp definiujemy wszystkie funkcje jakie zawarte są w powyższej definicji klasy. Pierwsze i najważniejsze funkcje realizują programowy interfejs SPI umożliwiający wysyłanie komend oraz danych do kontrolera wyświetlacza.

void NokiaLCD::spi_write(unsigned char data) {
CLK = 1;
for(int i = 0; i < 8; ++i){
CLK = 0;
if(data&0x80)
DATA = 1;
else
DATA = 0;
CLK = 1;
data <<= 1;
}
}

void NokiaLCD::spi_writeCommand(unsigned char cmd) {
CS = 0;
CLK = 0;
DATA = 0; // For commands the first bit is 0
spi_write(cmd);
CS = 1;
}

void NokiaLCD::spi_writeData(unsigned char data) {
CS = 0;
CLK = 0;
DATA = 1; // For data the first bit is 1
spi_write(data);
CS = 1;
}

Równie istotnym jest także konstruktor klasy NokiaLCD, który przyjmuje cztery argumenty typu PinName (nazwy linii GPIO) i wykorzystuje je przy inicjalizacji zmiennych typu DigitalOut (przeprowadzanej na liście inicjalizacyjnej). Wewnątrz konstruktora wykonywana jest jeszcze inicjalizacja wyświetlacza, przez co zaraz po utworzeniu obiektu klasy NokiaLCD można rysować dowolne elementy na ekranie.

// Hardware reset
RES = 0;
wait_ms(100);
RES = 1;
wait_ms(100);

// Sleep out
spi_writeCommand(SLEEPOUT);

// Inversion on
spi_writeCommand(INVOFF);

// Color Interface Pixel Format
spi_writeCommand(COLMOD);
spi_writeData(0x05); // 0x05 = 16 bits-per-pixel

// Memory access controler
spi_writeCommand(MADCTL);
spi_writeData(0x10);

wait_ms(10);

// Display On
spi_writeCommand(DISPON);
}

Rys. 5. Domyślna orientacja osi X i Y [3]

Użyta w powyższym kodzie komenda MADCTL pozwala ustawić orientację osi X i Y (rysunki 5 i 6), natomiast komenda COLMOD wybiera format w jakim będą przesyłane wartości kolorów i w danym przykładzie do tego celu są potrzebne 2 bajty (rysunek 7).

Rys. 6. Orientacja osi X i Y w zależności od wartości bitów danych komendy MADCTL [1]

 

Rys. 7. Przesyłanie wartości kolorów w formacie RGB565 [1]

Funkcja czyszcząca ekran zadanym kolorem za pomocą komend CASET oraz PASET ustawia zakres wartości współrzędnych w jakim będzie operować, a następnie w pętli zapisuje wartości poszczególnych pikseli. W przypadku ustawiania kontrastu wystarczy przy pomocy komendy SETCON wysłać wartość z przedziału od -64 do 63.

void NokiaLCD::clear(unsigned int color) {
// Set column address range
spi_writeCommand(CASET);
spi_writeData(0);
spi_writeData(131);

// Set page(row) address range
spi_writeCommand(PASET);
spi_writeData(0);
spi_writeData(131);

// Write memory
spi_writeCommand(RAMWR);
for(int i = 0; i < (132 * 132); i++)
{
// Write R[4:0] and G[5:3] spi_writeData( (color >> 8)&0xFF );
// Write G[2:0] and B[4:0] spi_writeData( color&0xFF );
}
}

void NokiaLCD::setContrast(char contrast) {
// Contrast value should be in range <-64…63>
spi_writeCommand(SETCON);
spi_writeData(contrast);
}

Funkcje do rysowania pojedynczych pikseli oraz wypełniania wskazanego obszaru pod względem implementacji wyglądają podobnie jak funkcja czyszczenia ekranu.

void NokiaLCD::drawPixel(int x, int y, unsigned int color) {
// Set column address range
spi_writeCommand(CASET);
spi_writeData(x);
spi_writeData(131);

// Set page(row) address range
spi_writeCommand(PASET);
spi_writeData(y);
spi_writeData(131);

// Write memory
spi_writeCommand(RAMWR);
// Write R[4:0] and G[5:3] spi_writeData( (color >> 8)&0xFF );
// Write G[2:0] and B[4:0] spi_writeData( color&0xFF );
}

void NokiaLCD::fillRect(int x, int y, int width, int height, unsigned int color) {
// Set column address range
spi_writeCommand(CASET);
spi_writeData(x);
spi_writeData(x + width – 1);

// Set page(row) address range
spi_writeCommand(PASET);
spi_writeData(y);
spi_writeData(y + height – 1);

// Write memory
spi_writeCommand(RAMWR);
for(int i = 0; i < (width * height); i++){
// Write R[4:0] and G[5:3] spi_writeData( (color >> 8)&0xFF );
// Write G[2:0] and B[4:0] spi_writeData( color&0xFF );
}
}

Wyświetlanie znaków jest bardziej złożoną operacją podczas której z tablicy czytane są wartości pikseli (0 – wolny, 1 – zajęty). Przykładowy fragment takiej tablicy przedstawiono poniżej, a na rysunku 8 pokazano efekt wyświetlenia symbolu „1” zakodowanego przy pomocy 8 bajtów.

 

Rys. 8. Symbol “1” zakodowany w tablicy; czcionka 6×8

Oraz odpowiednia funkcja wyświetlająca znaki:

unsigned char *FontTable[] = {(unsigned char *)FONT6x8, (unsigned char *)FONT8x8, (unsigned char *)FONT8x16};

unsigned char *pChar;
unsigned char *pFont;

unsigned int color;
unsigned char mask;
unsigned int nBytes;
unsigned int nCols;
unsigned int nRows;
unsigned char pixels;

// Get pointer to the beginning of the selected font table
pFont = (unsigned char *)FontTable[size];

// Get the nColumns, nRows and nBytes
nCols = *pFont;
nRows = *(pFont + 1);
nBytes = *(pFont + 2);

// Get pointer to the first byte of the desired character
pChar = pFont + (nBytes * (c – 0x1F));

// Set column address range
spi_writeCommand(CASET);
spi_writeData(x);
spi_writeData(x + nCols – 1);
// Set row address range
spi_writeCommand(PASET);
spi_writeData(y);
spi_writeData(y + nRows – 1);

// Write memory
spi_writeCommand(RAMWR);

// Loop on each row, working from the top to the bottom
for(int i = 0; i < nRows; ++i){
// Copy row of pixels from font table and then increment row
pixels = *pChar++;

// Set pixel mask
mask = 0x80;

// Loop on each pixel in the row, working from the left to the right
for(int j = 0; j < nCols; ++j){
// If pixel bit is set, use foreground color; else use the background color
if((pixels&mask) == 0)
color = bgColor;
else
color = fgColor;

mask >>= 1;

// Write R[4:0] and G[5:3] spi_writeData( (color >> 8)&0xFF );
// Write G[2:0] and B[4:0] spi_writeData( color&0xFF );
}
}
spi_writeCommand(NOP);
}

Pozostałe funkcje związane z wyprowadzaniem tekstu są intuicyjnie proste:

void NokiaLCD::gotoXY(int x, int y) {
text_x_last = text_x = x;
text_y_last = text_y = y;
}

void NokiaLCD::setTextBGColor(unsigned int color) {
text_bg_color = color;
}

void NokiaLCD::setTextFGColor(unsigned int color) {
text_fg_color = color;
}

void NokiaLCD::setTextSize(int size) {
text_size = size;
}

void NokiaLCD::writeChar(char c) {
drawChar(c, text_x, text_y, text_size, text_fg_color, text_bg_color);
}

void NokiaLCD::writeCharAtXY(char c, int x, int y) {
drawChar(c, x, y, text_size, text_fg_color, text_bg_color);
}

void NokiaLCD::writeString(char *s) {
int i(0), x_step(0);

switch(text_size){
case 0:{
x_step = 6;
break;
}
case 1: case 2:{
x_step = 8;
break;
}
default:{
x_step = 0;
break;
}
}

while(*s){
drawChar(*s++, text_x + x_step*i++, text_y, text_size, text_fg_color, text_bg_color);
}
}

void NokiaLCD::writeStringAtXY(char *s, int x, int y) {
int i(0), x_step(0);

switch(text_size){
case 0:{
x_step = 6;
break;
}
case 1: case 2:{
x_step = 8;
break;
}
default:{
x_step = 0;
break;
}
}

while(*s){
drawChar(*s++, x + x_step*i++, y, text_size, text_fg_color, text_bg_color);
}
}

Grafika w podejściu obiektowym

Przedstawiona w poprzedniej sekcji implementacja sterownika wyświetlacza oferuje dość skromne możliwości i należałoby ją rozbudować o dodatkowe funkcje, m.in. do rysowania linii oraz prostokątów. Takie zadanie można zrealizować na dwa sposoby. Pierwszy, typowy dla klasycznego języka C, polega na zdefiniowaniu funkcji, których prototypy mogłyby wyglądać w sposób następujący:

Jak można zauważyć funkcje zdefiniowane w ten drugi sposób są bardziej czytelne jednak wymagają zdefiniowania dwóch klas: Line oraz Rect. Klasa Line będzie zawierała pola typu int przechowujące położenie punktów początku i końca odcinka prostej, konstruktory za pomocą których będzie można utworzyć obiekt Line, a także funkcje umożliwiające przesunięcie oraz przeskalowanie linii. Klasa Rect będzie wyglądać podobnie z tą różnicą, że zamiast współrzędnych i wymiarów będzie przechowywać cztery podobiekty typu Line. W celu uzyskania bardziej przejrzystego projektu zdefiniujemy te klasy w pliku Graphics.h:

class Line {
public:
int x1, y1;
int x2, y2;

Line();
Line(int lx1, int ly1, int lx2, int ly2);

Line operator*=(const double scaleFactor);
void move(int dx, int dy);
void moveTo(int lx, int ly);
};

class Rect {
public:
Line bottom, left, right, top;

Rect(int lx, int ly, int lwidth, int lheight);

Rect operator*=(const double scaleFactor);
void move(int dx, int dy);
void moveTo(int lx, int ly);
};

#endif /* GRAPHICS_H */

W pliku implementacyjnym Graphics.cpp definiujemy konstruktory oraz funkcje klas powyższych. W przypadku skalowania obu współrzędnych zastosujemy przeciążanie operatora „*=” (mnożenie z przypisaniem wartości wyniku).

Line::Line(int lx1, int ly1, int lx2, int ly2)
: x1(lx1), y1(ly1), x2(lx2), y2(ly2) {
// Po prostu inicjalizacja zmiennych
}

Line Line::operator*=(const double scaleFactor) {
// Wymnożenie długości rzutów linii przez współczynnik scaleFactor
x2 = ((double)(x2 – x1))*scaleFactor + x1;
y2 = ((double)(y2 – y1))*scaleFactor + y1;

return *this;
}

void Line::move(int dx, int dy) {
// Przesunięcie linii o (dx,dy)
x1 += dx;
y1 += dy;

x2 += dx;
y2 += dy;
}

void Line::moveTo(int lx, int ly) {
// Przesunięcie punktu początkowego linii (x1,y1) do punktu (lx,ly)
x2 = lx + (x2 – x1);
y2 = ly + (y2 – y1);

x1 = lx;
y1 = ly;
}

Rect::Rect(int lx, int ly, int lwidth, int lheight) {
// Utworzenie czterech obiektów typu Line stanowiących prostokąt
bottom = Line(lx, ly + lheight, lx + lwidth, ly + lheight);
left = Line(lx, ly, lx, ly + lheight);
right = Line(lx + lwidth, ly, lx + lwidth, ly + lheight);
top = Line(lx, ly, lx + lwidth, ly);
}

Rect Rect::operator*=(const double scaleFactor) {
// Skalowanie długości krawędzi prostokąta
bottom *= scaleFactor;
left *= scaleFactor;
right *= scaleFactor;
top *= scaleFactor;

// Przesunięcie dwóch krawędzi prostokąta
bottom.moveTo(left.x2, left.y2);
right.moveTo(top.x2, top.y2);

return *this;
}

void Rect::move(int dx, int dy) {
// Przesunięcie prostokąta o (dx,dy)
bottom.move(dx, dy);
left.move(dx, dy);
right.move(dx, dy);
top.move(dx, dy);
}

void Rect::moveTo(int lx, int ly) {
// Przesunięcie punktu początkowego prostokąta (x1,y1) (wraz z prostokątem) do punktu (lx,ly)
left.moveTo(lx, ly);
top.moveTo(lx, ly);

bottom.moveTo(left.x2, left.y2);
right.moveTo(top.x2, top.y2);
}

Funkcje rysujące linie oraz prostokąty (plik NokiaLCD.cpp) wyglądają jak poniżej:

// Wyznaczenie różnicy między współrzędnymi punktów
int dx = x2 – x1;
int dy = y2 – y1;

// Rysuj punkt początkowy
drawPixel(x1, y1, color);

// Zmienną niezależną jest x
if( (abs(dx) >= abs(dy)) && abs(dx) != 0){
float a = (float) dy / (float) dx; // Wyznaczenie współczynnika kierunkowego
float b = y1 – a*x1; // Wyznaczenie przesunięcia
dx = (dx < 0) ? -1 : 1; // W zależności od tego który punkt znajduje
// się “wyżej” rysowanie będzie odbywać się w
// kierunku dodatnich lub ujemnych wartości

// Rysuj kolejne punkty, dopóki nie osiągnięto punktu x2
while(x1 != x2){
x1 += dx;
drawPixel(x1, (a*x1 + b), color);
}
}
// Zmienną niezależną jest y
else{
float a = (float) dx / (float) dy; // Wyznaczenie współczynnika kierunkowego
float b = x1 – a*y1; // Wyznaczenie przesunięcia
dy = (dy < 0) ? -1 : 1;

// Rysuj kolejne punkty, dopóki nie osiągnięto punktu y2
while(y1 != y2){
y1 += dy;
drawPixel((a*y1 + b), y1, color);
}
}
}

void NokiaLCD::drawRectangle(const Rect& rect, unsigned int color, unsigned char fill) {
int x1 = rect.top.x1;
int y1 = rect.top.y1;
int x2 = rect.bottom.x2;
int y2 = rect.bottom.y2;

// Wyznaczenie dlugosci bokow
int a = abs(x2 – x1);
int b = abs(y2 – y1);

switch(fill){
// Rysuj pełny prostokąt z wypełnieniem zadanym kolorem
case 1:{
for(int i=0; i < a; i++){
for(int j=0; j < b; j++){
drawPixel(x1+i, y1+j, color);
}
}
break;
}
// Rysuj 4 boki prostokąta z zadanym kolorem
default:{
drawLine(rect.top, color);
drawLine(rect.bottom, color);
drawLine(rect.left, color);
drawLine(rect.right, color);
break;
}
}
}

Możliwość przeciążania operatorów w języku C++ pozwala nie tylko na przypisanie nowych funkcji operatorom w zależności od typu przyjmowanych argumentów, ale także na skrócenie (i zwiększenie czytelności) kodu klienckiego jaki tworzy użytkownik. Tak na przykład, aby wyświetlić tekst, wartość, linię oraz prostokąt można użyć następujących komend:

zamiast:

Przykładowe implementacje przeciążonych operatorów przedstawia następujący listing:

NokiaLCD NokiaLCD::operator<<(const Line& line) {
drawLine(line, fg_color);
return *this;
}

NokiaLCD NokiaLCD::operator<<(const Rect& rect) {
drawRectangle(rect, fg_color, 0);
return *this;
}

/* ****************************** */
/* *** Text-related functions *** */
/* ****************************** */

NokiaLCD NokiaLCD::operator<<(const char c) {
int x_step(0);

switch(text_size){
case 0:{
x_step = 6;
break;
}
case 1: case 2:{
x_step = 8;
break;
}
default:{
x_step = 0;
break;
}
}

drawChar(c, text_x_last, text_y_last, text_size, text_fg_color, text_bg_color);

text_x_last += x_step;

return *this;
}

NokiaLCD NokiaLCD::operator<<(const char *s) {
int i(0), x_step(0);

switch(text_size){
case 0:{
x_step = 6;
break;
}
case 1: case 2:{
x_step = 8;
break;
}
default:{
x_step = 0;
break;
}
}

while(*s){
drawChar(*s++, text_x_last + x_step*i, text_y_last, text_size, text_fg_color, text_bg_color);
++i;
}

text_x_last += x_step*i;

return *this;
}

NokiaLCD NokiaLCD::operator<<(const int n) {
int i(0), x_step(0);
char text[16];

sprintf(text, “%d”, n);

switch(text_size){
case 0:{
x_step = 6;
break;
}
case 1: case 2:{
x_step = 8;
break;
}
default:{
x_step = 0;
break;
}
}

while(text[i]){
drawChar(text[i], text_x_last + x_step*i, text_y_last, text_size, text_fg_color, text_bg_color);
++i;
}

text_x_last += x_step*i;

return *this;
}

public:
//[…]

// Graphics-related functions
NokiaLCD operator<<(const Line& line);
NokiaLCD operator<<(const Rect& rect);
//[…]

// Text-related functions
NokiaLCD operator<<(const char c);
NokiaLCD operator<<(const char *s);
NokiaLCD operator<<(const int n);
//[…] };

Oczywiście przedstawiona realizacja sterownika wyświetlacza nie jest idealnie dopracowana, jednak pokazuje z grubsza specyfikę i możliwości programowania w języku C++, zwalniając nas z tworzenia nadmiernej ilości kodu oraz posługiwania się złożonymi wskaźnikami. W ramach prostych ćwiczeń można napisać pozostałe funkcje rysujące, zmodyfikować istniejące, a także nadać nowe znaczenia niektórym operatorom.

Przykładowa aplikacja testowa

Na zakończenie, na poniższym listingu przedstawiono program testowy, który sprawdzi poprawność napisanych funkcji graficznych. Po skompilowaniu projektu, zaprogramowaniu i uruchomieniu układu [5] w górnej części jest wyświetlany tekst statyczny, poniżej powinien znajdować się prostokąt z krzyżykiem przesuwający się co 200 ms w lewo/prawo, a na samym dole z taką samą częstotliwością (5 Hz) wyświetlane są literki na różnych tłach umożliwiające dostrojenie kontrastu przy pomocy wciśnięcia i przytrzymania przycisku User Button w taki sposób, aby wszystkie 3 kolory były wyraźne.

Fot. 9. Przykładowa aplikacja testująca sterownik wyświetlacza

DigitalIn button(USER_BUTTON);

int main() {
char c(‘a’);
int contrast(50);
int dx(0), sign(1);

// Create NokiaLCD object and initialize LCD
NokiaLCD lcd(D10, D13, D11, D9);

// Set LCD contrast
lcd.setContrast(contrast);

// Clear LCD using white color
lcd.clear(LCD_RGB565_WHITE);

// Draw filled rectangles
lcd.fillRect(5, 5, 122, 80, LCD_RGB565_YELLOW);
lcd.fillRect(8, 8, 116, 28, LCD_RGB565_ORANGE);
lcd.fillRect(8, 38, 116, 10, LCD_RGB565_GRAY);

// Set background and foreground color for text
lcd.setTextBGColor(LCD_RGB565_ORANGE);
lcd.setTextFGColor(LCD_RGB565_BLACK);

// Set font: 8×8
lcd.setTextSize(1);

// Write text @ x=10, y=10
lcd.gotoXY(10, 10);
lcd.writeString(“Nucleo-F334R8″);
// Write text @ x=10, y=20
lcd.gotoXY(10, 20);
lcd.writeString(” + KAmodTFT2″);

lcd.setFGColor(LCD_RGB565_BLACK);

Line line_1(17, 67, 23, 73);
Line line_2(23, 67, 17, 73);
Rect rect_1(10, 65, 20, 10);

lcd << line_1 << line_2 << rect_1;

// Set font: 6×8
lcd.setTextSize(0);

// Set background and foreground color for text
lcd.setTextBGColor(LCD_RGB565_GRAY);
lcd.setTextFGColor(LCD_RGB565_WHITE);

// Write text @ x=10, y=40
lcd.gotoXY(10, 40);
lcd << “Contrast: \0″ << contrast << ” “;

while(1){
// Wait 200 ms
wait(0.2);

// If button was pressed
if(!button){
// Increment contrast
if(++contrast > 63)
contrast = -64;

lcd.setContrast(++contrast);

// Write text @ x=10, y=40
lcd.gotoXY(10, 40);
lcd << “Contrast: \0″ << contrast << ” “;
}

// Increment character
if(++c > ‘z’)
c = ‘a’;

// Write characters
lcd.drawChar(c, 15, 90, 0, LCD_RGB565_RED, LCD_RGB565_WHITE);
lcd.drawChar(c, 35, 90, 0, LCD_RGB565_GREEN, LCD_RGB565_WHITE);
lcd.drawChar(c, 55, 90, 0, LCD_RGB565_BLUE, LCD_RGB565_WHITE);
lcd.drawChar(c, 75, 90, 0, LCD_RGB565_BLACK, LCD_RGB565_WHITE);
lcd.drawChar(c, 95, 90, 0, LCD_RGB565_WHITE, LCD_RGB565_BLACK);

lcd.drawChar(c, 15, 100, 0, LCD_RGB565_WHITE, LCD_RGB565_RED);
lcd.drawChar(c, 35, 100, 0, LCD_RGB565_WHITE, LCD_RGB565_GREEN);
lcd.drawChar(c, 55, 100, 0, LCD_RGB565_WHITE, LCD_RGB565_BLUE);
lcd.drawChar(c, 75, 100, 0, LCD_RGB565_WHITE, LCD_RGB565_BLACK);
lcd.drawChar(c, 95, 100, 0, LCD_RGB565_BLACK, LCD_RGB565_WHITE);

// Clear old objects
lcd.setFGColor(LCD_RGB565_YELLOW);
lcd << line_1 << line_2 << rect_1;

dx += sign;
if(dx >= 80)
sign = -1;
else if(dx <= 0)
sign = 1;

// Move objects
line_1.move(sign, 0);
line_2.move(sign, 0);
rect_1.move(sign, 0);

// Draw new objects
lcd.setFGColor(LCD_RGB565_BLACK);
lcd << line_1 << line_2 << rect_1;
}
}

Jan Szemiet

Literatura

[1] – Dokumentacja sterownika PCF8833

[2] – http://stm32.eu/node/36 – Obsługa wyświetlacza telefonu Nokia 6100 ze sterownikiem Epson S1D15G00: programowa obsługa wyświetlacza

[3] – http://stm32.eu/node/46 – Obsługa wyświetlacza telefonu Nokia 6100 ze sterownikiem Epson S1D15G00: organizacja pamięci

[4] – http://stm32.eu/node/201 – ZL31ARM: implementacja funkcji graficznych 2D

[5] – Nucleo-F334R8 – Właściwości i praca z zestawem

Do pobrania

Autor: