Starten Sie das transflektive TFT-Display auf der SSD1283A mit STM32

Einleitung


Das Anzeigemodell heißt H016IT01. Diese Anzeige ist vor allem deshalb interessant, weil sie transflektiv ist. Dies bedeutet, dass das darauf befindliche Bild auch bei strahlender Sonne sichtbar sein sollte. Und dies ist auch fast das einzige verfügbare Modell mit dieser Funktion auf der berühmten chinesischen Website.

Der Artikel wurde jedoch veröffentlicht, da es nur sehr wenige Informationen zum SSD1283A-Controller gibt (sowohl im russischen als auch im westlichen Segment des Netzwerks), und ich habe nirgendwo eine Anleitung gesehen. Sie können ein Datenblatt im Netzwerk finden, es gibt jedoch keine Informationen zum Initialisieren und Arbeiten mit dem Display, und nur eine Beschreibung der Register ist hilfreich.

Ich möchte betonen, dass dieses Material sicherlich nicht die ultimative Wahrheit ist. Ich gebe nur meine Erfahrung in der Interaktion mit dem Gerät. Das Hauptziel des Artikels ist einfach - allen, die sich entscheiden, wollen oder mit diesem Display arbeiten wollen, nicht mehr zu helfen.

Bild

Das Display verfügt über 8 Pins:

1) GND
2) VCC - 5 oder 3,3V
3) CS - SPI Chip Select
4) RST - "0" - schaltet die Anzeige aus, "1" - schaltet ein.
5) A0 / DC - Datenbefehl ("0" - Befehl, "1" - Daten)
6) SDA - SPI MOSI
7) SCK - SPI SCK
8) LED - Hintergrundbeleuchtung, wie VCC, von 3,3 bis 5V

Sie müssen das Display mit SPI programmieren, das Discovery Board auf stm32f407 hilft mir dabei.

SPI


Obwohl ich über SPI mit SSD1283A interagiert habe, ist anzumerken, dass der Controller auch eine parallele Schnittstelle bietet, die von diesem Anzeigemodell jedoch nicht unterstützt wird. Sein SPI ist auch nicht gewöhnlich, es hat nur eine SDA-Datenleitung. In der Tat ist dies die MOSI-Linie, was bedeutet, dass wir nichts von der Anzeige zählen können, was das Datenblatt uns sagt.

Bild

Zuerst werden wir SPI konfigurieren, um SPI1 und GPIO anzugreifen, die Beine von SDA und SCK als alternative Funktion konfigurieren (ich habe auch MISO durchgeführt, dies ist jedoch nicht erforderlich). Die Betriebsart ist als unidirektionaler Gebermaster konfiguriert.

BIT_BAND_PER(RCC->AHB1ENR ,RCC_AHB1ENR_GPIOAEN) = true; if(SPI_NUM::_1 == spi) { /*!<-------------Enable spi clocking--------------->!*/ BIT_BAND_PER(RCC->APB2ENR, RCC_APB2ENR_SPI1EN) = true; /*!<----PA5 - SCK, PA6 - MISO, PA7 - MOSI---->!*/ GPIOA->MODER |= (GPIO_MODER_MODE5_1 | GPIO_MODER_MODE6_1 | GPIO_MODER_MODE7_1); GPIOA->OSPEEDR |= (GPIO_OSPEEDR_OSPEED5_1 | GPIO_OSPEEDR_OSPEED7_1); GPIOA->AFR[0] |= (GPIO_AFRL_AFSEL5_0 | GPIO_AFRL_AFSEL5_2 | GPIO_AFRL_AFSEL6_0 | GPIO_AFRL_AFSEL6_2 | GPIO_AFRL_AFSEL7_0 | GPIO_AFRL_AFSEL7_2); /*!<-----customize SPI------>!*/ SPI1->CR1 |= (SPI_CR1_BIDIMODE | SPI_CR1_BIDIOE | SPI_CR1_SSM | SPI_CR1_SSI /*| SPI_CR1_DFF*/ | SPI_CR1_MSTR); BIT_BAND_PER(SPI1->CR1 ,SPI_CR1_SPE) = true; } 


Dann schreiben wir eine einfache Bytetransferfunktion per SPI:
 void stm32f407_spi::stm32f407_spi_send(uint8_t data) { SPI1->DR = data; while((SPI1->SR & SPI_SR_BSY)) continue; } 

Dies reicht aus, damit SPI in dem von uns benötigten Modus arbeitet und Daten mit der höchstmöglichen Geschwindigkeit überträgt.

Initialisierung anzeigen


Jetzt ist es an der Zeit, die Anzeige zu initialisieren. Senden Sie dazu eine bestimmte Folge von Befehlen. Ein Befehl besteht aus 3 Bytes, wobei ein Byte die Befehlsnummer und die anderen beiden Daten sind. Sie müssen auch Pin A0 umschalten, wenn Sie das Befehlsbyte an „0“ und die Daten an „1“ senden. Der Einfachheit halber habe ich Inline-Funktionen erstellt, um den Status der RST-, A0- und CS-Pins des Displays zu ändern.

 enum class DC : uint8_t { COMMAND, DATA }; #pragma inline=forced inline void tft_lcd_rst(bool rst) {BIT_BAND_PER(GPIOA->ODR , GPIO_ODR_OD2) = rst;} #pragma inline=forced inline void tft_lcd_dc(DC dc) {BIT_BAND_PER(GPIOA->ODR , GPIO_ODR_OD3) = static_cast<bool>(dc);} #pragma inline=forced inline void tft_lcd_cs(bool cs) {BIT_BAND_PER(GPIOA->ODR , GPIO_ODR_OD4) = cs;} 


Dann sieht die Kommandozeile so aus:
 void tft_lcd::tft_lcd_send(uint8_t addr, uint16_t data) { this->tft_lcd_dc(DC::COMMAND); stm32f407_spi_send(addr); this->tft_lcd_dc(DC::DATA); stm32f407_spi_send(static_cast<uint8_t>(data >> 8)); stm32f407_spi_send(static_cast<uint8_t>(data)); } 

Um zu initialisieren, müssen Sie die folgende Folge von Befehlen übermitteln, die ich in den Arduino-Bibliotheken für diesen Bildschirm ausspioniert habe:

Initialisierungsbefehle
 static constexpr uint8_t TFT_DELAY = 0xFF; static constexpr t_tft_regs tft_regs[]= { { 0x10, 0x2F8E }, { 0x11, 0x000C }, { 0x07, 0x0021 }, { 0x28, 0x0006 }, { 0x28, 0x0005 }, { 0x27, 0x057F }, { 0x29, 0x89A1 }, { 0x00, 0x0001 }, { TFT_DELAY, 100 }, { 0x29, 0x80B0 }, { TFT_DELAY, 30 }, { 0x29, 0xFFFE }, { 0x07, 0x0223 }, { TFT_DELAY, 30 }, { 0x07, 0x0233 }, { 0x01, 0x2183 }, { 0x03, 0x6830 }, { 0x2F, 0xFFFF }, { 0x2C, 0x8000 }, { 0x27, 0x0570 }, { 0x02, 0x0300 }, { 0x0B, 0x580C }, { 0x12, 0x0609 }, { 0x13, 0x3100 }, }; 


Dabei bedeutet TFT_DELAY einfach Sleep die angegebene Menge von mS. Wenn Sie sich mit dem Datenblatt befassen, werden solche Daten für einige Adressen seltsam erscheinen, da viele Adressen in reservierte Bits schreiben.

Lassen Sie zum Initialisieren "0" auf CS, starten Sie das Display neu (Pin RST) und gehen Sie die Befehlstabelle durch.

  tft_lcd_rst(false); delay(5, mS); tft_lcd_rst(true); delay(200, mS); this->tft_lcd_cs(false); delay(5, mS); /*!<--------Init display---------->!*/ for(uint8_t i = 0; i < sizeof(tft_regs)/sizeof(tft_regs[0]) ;i++) { (TFT_DELAY != tft_regs[i].address) ? (this->tft_lcd_send(tft_regs[i].address, tft_regs[i].value)): (delay(tft_regs[i].value, mS)); } delay(5, mS); this->tft_lcd_cs(true); 

Danach sollte das Bild das Bild von weißer zu grauer Farbfernsehstörung ändern.

Bild

Zeichne ein Rechteck


Mit dem SSD1283A-Controller können Sie Bilder mit Rechtecken zeichnen, für die 4 Befehle verwendet werden. Der Befehl 0x44 enthält die Koordinate des Endes und des Beginns des Rechtecks ​​entlang der Abszissenachse im hohen bzw. niedrigen Datenbyte. Der Befehl 0x45 ist für die Ordinatenachse identisch. Der Befehl 0x21 enthält die Koordinate des anfänglichen Renderns im oberen Byte für y und im unteren Byte für x. Der Befehl 0x22 enthält die Farbe für das aktuelle Pixel. Dies bedeutet, dass es für jedes Pixel des aktuellen Rechtecks ​​wiederholt werden muss. Das Display hat auch eine Funktion, obwohl es selbst eine Auflösung von 130x130 hat, sein virtuelles Koordinatengitter Abmessungen von 132x132 hat und die Koordinaten ab dem Punkt 2x2 zählen.

Wenn wir zum Beispiel ein schwarzes Quadrat 20 mal 20 zeichnen möchten, dessen Startpunkt an der Position (30, 45) liegt, müssen wir die folgende Befehlsfolge senden:

0x44 0x3320 (30 + 20 + 2-1, 30 + 2)
0x45 0x422F (45 + 20 + 2-1, 45 + 2)
0x21 0x2F20
0x22 0x0000, und dieser Befehl muss 400 (20 * 20) Mal gesendet werden.

Dann sieht die Rechteck-Zeichen-Funktion so aus (vorausgesetzt, die Koordinaten sind bereits um 2 verschoben):

 void tft_lcd::draw_rect(const t_rect& rec) { this->tft_lcd_send(0x44, ((rec.x1 - 1) << 8) | rec.x0); this->tft_lcd_send(0x45, ((rec.y1 - 1) << 8) | rec.y0); this->tft_lcd_send(0x21, (rec.y0 << 8) | rec.x0); for(uint16_t i = 0; i < ((rec.x1 - rec.x0) * (rec.y1 - rec.y0)); i++) { this->tft_lcd_send(0x22, rec.col); } } 

Um ein Rechteck zu zeichnen, geben Sie einfach die Koordinaten der Ecken und die Farbe an. Ein Beispiel für das Füllen des gesamten Bildschirms mit Rosa sieht folgendermaßen aus:

 t_rect rect = {0x02, 0x02, 0x84, 0x84, static_cast<uint16_t>(COLOUR::MAGENTA)}; this->draw_rect(rect); 

Ergebnis:

Bild

Zeichne Buchstaben und Zahlen


Erstellen Sie eine Liste von Arrays mit Koordinaten für die Zeichen.

Anordnungen von Koordinaten von Buchstaben und Zahlen
 namespace rect_coord_lit { const t_rect_coord comma[] = {{0, 20, 5, 25}, {3, 25, 5, 28}}; const t_rect_coord dot[] = {{0, 20, 5, 25}}; const t_rect_coord space[] = {{0, 0, 0, 0}}; const t_rect_coord _0[] = {{0, 0, 15, 5},{0, 5, 5, 25},{5, 20, 15, 25},{10, 5, 15, 20}}; const t_rect_coord _1[] = {{10, 0, 15, 25}}; const t_rect_coord _2[] = {{0, 0, 15, 5},{10, 5, 15, 15},{0, 10, 10, 15},{0, 15, 5, 25},{5, 20, 15, 25}}; const t_rect_coord _3[] = {{0, 0, 15, 5},{10, 5, 15, 25},{0, 10, 10, 15},{0, 20, 10, 25}}; const t_rect_coord _4[] = {{0, 0, 5, 15},{5, 10, 10, 15},{10, 0, 15, 25}}; const t_rect_coord _5[] = {{0, 0, 15, 5},{0, 5, 5, 15},{0, 10, 15, 15},{10, 15, 15, 25},{0, 20, 10, 25}}; const t_rect_coord _6[] = {{0, 0, 15, 5},{0, 5, 5, 25},{5, 10, 10, 15},{5, 20, 10, 25},{10, 10, 15, 25}}; const t_rect_coord _7[] = {{0, 0, 15, 5},{10, 5, 15, 25}}; const t_rect_coord _8[] = {{0, 0, 15, 5},{0, 5, 5, 25},{5, 20, 15, 25},{10, 5, 15, 20},{5, 10, 10, 15}}; const t_rect_coord _9[] = {{0, 0, 15, 5},{0, 5, 5, 15},{0, 20, 15, 25},{10, 5, 15, 20},{5, 10, 10, 15}}; const t_rect_coord a[] = {{0, 10, 5, 25},{5, 5, 10, 10},{5, 15, 10, 20},{10, 10, 15, 25}}; const t_rect_coord b[] = {{0, 0, 5, 25},{5, 10, 15, 15},{10, 15, 15, 20},{5, 20, 15, 25}}; const t_rect_coord c[] = {{0, 5, 15, 10},{0, 10, 5, 20},{0, 20, 15, 25}}; const t_rect_coord d[] = {{0, 10, 10, 15},{0, 15, 5, 20},{0, 20, 10, 25}, {10, 0, 15, 25}}; const t_rect_coord e[] = {{0, 5, 15, 8}, {0, 12, 15, 15}, {0, 8, 5, 25}, {10, 8, 15, 12}, {5, 20, 15, 25}}; const t_rect_coord f[] = {{5, 5, 10, 25},{5, 0, 15, 5},{0, 10, 15, 15}}; const t_rect_coord g[] = {{0, 5, 5, 20}, {5, 5, 10, 10}, {5, 15, 10, 20}, {10, 5, 15, 30}, {0, 25, 10, 30}}; const t_rect_coord h[] = {{0, 0, 5, 25},{5, 10, 15, 15},{10, 15, 15, 25}}; const t_rect_coord i[] = {{5, 3, 10, 8},{5, 10, 10, 25}}; const t_rect_coord j[] = {{5, 3, 10, 8},{5, 10, 10, 30}, {0, 25, 5, 30}}; const t_rect_coord k[] = {{0, 0, 5, 25},{5, 15, 10, 20}, {10, 10, 15, 15}, {10, 20 , 15, 25}}; const t_rect_coord l[] = {{5, 0, 10, 25}}; const t_rect_coord m[] = {{0, 10, 4, 25},{7, 10, 10, 25}, {13, 10, 17,25}, {0, 5 , 12, 10}}; const t_rect_coord n[] = {{0, 10, 5, 25},{10, 10, 15, 25}, {0, 5 , 10, 10}}; const t_rect_coord o[] = {{0, 5, 5, 25}, {10, 5, 15, 25}, {5, 5, 10, 10}, {5, 20, 10, 25}}; const t_rect_coord p[] = {{0, 5, 5, 30}, {5, 5, 15, 10}, {5, 15, 15, 20}, {10, 10, 15, 15}}; const t_rect_coord q[] = {{0, 5, 5, 20}, {5, 5, 15, 10}, {5, 15, 15, 20}, {10, 10, 15, 30}}; const t_rect_coord r[] = {{0, 10, 5, 25},{5, 5, 15, 10}}; const t_rect_coord s[] = {{3, 5, 15, 10}, {0, 8, 5, 13}, {3, 13, 12, 17}, {10, 17, 15, 22}, {0, 20, 12, 25}}; const t_rect_coord t[] = {{5, 0, 10, 25},{0, 5, 15, 10},{10, 20, 15, 25}}; const t_rect_coord u[] = {{0, 5, 5, 25},{10, 5, 15, 25},{5, 20, 10, 25}}; const t_rect_coord v[] = {{0, 5, 5, 15}, {10, 5, 15, 15}, {1, 15, 6, 20}, {9, 15, 14, 20}, {5, 20, 10, 25}}; const t_rect_coord w[] = {{0, 5, 4, 20},{7, 5, 10, 20}, {13, 5, 17, 20}, {4, 20 , 7, 25}, {10, 20 , 13, 25}}; const t_rect_coord x[] = {{0, 5, 5, 10},{10, 5, 15, 10}, {0, 20, 5, 25}, {10, 20 , 15, 25}, {5, 10 , 10, 20}}; const t_rect_coord y[] = {{0, 5, 5, 20}, {5, 15, 10, 20}, {10, 5, 15, 30}, {0, 25, 10, 30}}; const t_rect_coord z[] = {{0, 5, 15, 10}, {10, 10, 15, 13}, {5, 12, 10, 18}, {0, 17, 5, 20}, {0, 20, 15, 25}}; } 


Erstellen Sie eine Tabelle, in der wir das Zeichen selbst (in ASCII), die Anzahl seiner Rechtecke und seine Koordinaten korrelieren:

Koordinatenzeigertabelle
 typedef struct { char lit; uint8_t size; const t_rect_coord *rec_coord; }t_rect_coord_table; #define LITERAL_COORD(x) sizeof(x)/ sizeof(x[0]), x const t_rect_coord_table rect_coord_table[] = { {',', LITERAL_COORD(rect_coord_lit::comma)}, {'.', LITERAL_COORD(rect_coord_lit::dot)}, {'.', LITERAL_COORD(rect_coord_lit::dot)}, {' ', LITERAL_COORD(rect_coord_lit::space)}, {'0', LITERAL_COORD(rect_coord_lit::_0)}, {'1', LITERAL_COORD(rect_coord_lit::_1)}, {'2', LITERAL_COORD(rect_coord_lit::_2)}, {'3', LITERAL_COORD(rect_coord_lit::_3)}, {'4', LITERAL_COORD(rect_coord_lit::_4)}, {'5', LITERAL_COORD(rect_coord_lit::_5)}, {'6', LITERAL_COORD(rect_coord_lit::_6)}, {'7', LITERAL_COORD(rect_coord_lit::_7)}, {'8', LITERAL_COORD(rect_coord_lit::_8)}, {'9', LITERAL_COORD(rect_coord_lit::_9)}, {'a', LITERAL_COORD(rect_coord_lit::a)}, {'b', LITERAL_COORD(rect_coord_lit::b)}, {'c', LITERAL_COORD(rect_coord_lit::c)}, {'d', LITERAL_COORD(rect_coord_lit::d)}, {'e', LITERAL_COORD(rect_coord_lit::e)}, {'f', LITERAL_COORD(rect_coord_lit::f)}, {'g', LITERAL_COORD(rect_coord_lit::g)}, {'h', LITERAL_COORD(rect_coord_lit::h)}, {'i', LITERAL_COORD(rect_coord_lit::i)}, {'j', LITERAL_COORD(rect_coord_lit::j)}, {'k', LITERAL_COORD(rect_coord_lit::k)}, {'l', LITERAL_COORD(rect_coord_lit::l)}, {'m', LITERAL_COORD(rect_coord_lit::m)}, {'n', LITERAL_COORD(rect_coord_lit::n)}, {'o', LITERAL_COORD(rect_coord_lit::o)}, {'p', LITERAL_COORD(rect_coord_lit::p)}, {'q', LITERAL_COORD(rect_coord_lit::q)}, {'r', LITERAL_COORD(rect_coord_lit::r)}, {'s', LITERAL_COORD(rect_coord_lit::s)}, {'t', LITERAL_COORD(rect_coord_lit::t)}, {'u', LITERAL_COORD(rect_coord_lit::u)}, {'v', LITERAL_COORD(rect_coord_lit::v)}, {'w', LITERAL_COORD(rect_coord_lit::w)}, {'x', LITERAL_COORD(rect_coord_lit::x)}, {'y', LITERAL_COORD(rect_coord_lit::y)}, {'z', LITERAL_COORD(rect_coord_lit::z)} }; 


Dann sehen die Zeichenfunktionen eines Zeichens so aus:

 void tft_lcd::draw_lit(char ch, const t_rect_coord *rect_coord, uint16_t colour, uint16_t x0, uint16_t y0, uint8_t size) { t_rect rec = {0}; rec.col = colour; uint8_t ctr = size; uint8_t i = 0; while(ctr--) { rec.x0 = x0 + rect_coord[i].x0; rec.y0 = y0 + rect_coord[i].y0; rec.x1 = x0 + rect_coord[i].x1; rec.y1 = y0 + rect_coord[i].y1; i++; this->draw_rect(rec); } } void tft_lcd::draw_char(char ch, uint16_t colour, uint16_t x0, uint16_t y0) { x0 += 2; y0 +=2; for(const auto &field : rect_coord_table) { if(field.lit == ch) { draw_lit(ch, field.rec_coord, colour, x0, y0, field.size); } } } 

Die Funktion draw_char repräsentiert eine Zustandsmaschine, bei der eine for-Schleife jedes Feld der Koordinatentabelle durchläuft und nach einer Übereinstimmung anhand des Symbols sucht. Wird eine Übereinstimmung gefunden, werden die Daten an die Funktion draw_lit übergeben, die die gewünschte Anzahl von Rechtecken an den angegebenen Koordinaten zeichnet.

Die Strichzeichnung sieht folgendermaßen aus:
 void tft_lcd::draw_string(char *ch, COLOUR colour, uint16_t x0, uint16_t y0) { while(*ch) { this->draw_char(*ch, static_cast<uint16_t>(colour), x0, y0); x0+= 20; ch++; } } 

Jetzt genügt es, die Linie, ihre Anfangsposition und Farbe festzulegen. Um neue Buchstaben hinzuzufügen, erstellen Sie einfach ein Array von Koordinaten und fügen Sie es der Tabelle hinzu. Ein Beispiel für zufällige Zeilen nach Code:

 this->draw_string("123456", COLOUR::GREEN, 5, 0); this->draw_string("habr,.", COLOUR::WHITE, 5, 30); this->draw_string("abcdef", COLOUR::RED, 5, 60); this->draw_string("stwxyz", COLOUR::YELLOW, 5, 90); 

Bild

In diesem Modus ist die Anzeige schnell, das Auge bemerkt den Zeichenvorgang nicht.

Für alle Interessierten kann der vollständige Projektcode unter dem Link eingesehen werden .

Source: https://habr.com/ru/post/de482610/


All Articles