Lancez l'écran TFT transflectif sur SSD1283A avec STM32

Présentation


Le modèle d'affichage est appelé H016IT01. Cet affichage est intéressant principalement parce qu'il est transflectif. Cela signifie que l'image doit être visible même sous le soleil brillant. Et c'est aussi presque le seul modèle disponible avec cette fonctionnalité sur le célèbre site chinois.

Mais l'article a été publié car il y a très peu d'informations sur le contrôleur SSD1283A (à la fois dans les segments russe et occidental du réseau), et je n'ai vu aucun guide nulle part. Vous pouvez trouver une fiche technique sur le réseau, mais il n'y a aucune information sur l'initialisation et l'utilisation de l'affichage, et seules les descriptions des registres sont utiles.

Je tiens à souligner que ce matériau n'est certainement pas la vérité ultime. Je donne uniquement mon expérience d'interaction avec l'appareil. L'objectif principal de l'article est simple - pour aider tous ceux qui décident, veulent ou veulent travailler avec cet affichage, pas plus.

image

L'écran a 8 broches:

1) GND
2) VCC - 5 ou 3,3 V
3) CS - SPI Chip Select
4) RST - «0» - éteint l'affichage, «1» - s'allume.
5) A0 / DC - Commande de données («0» - commande, «1» - données)
6) SDA - SPI MOSI
7) SCK - SPI SCK
8) LED - sortie rétro-éclairage, comme VCC, de 3,3 à 5 V

Vous devez programmer l'affichage à l'aide de SPI, la carte de découverte sur stm32f407 m'aidera à ce sujet.

SPI


Bien que j'interagisse avec SSD1283A via SPI, il convient de noter que le contrôleur fournit également une interface parallèle, mais ce modèle d'affichage ne le prend pas en charge. Son SPI n'est pas ordinaire non plus, il n'a qu'une seule ligne de données SDA. En fait, c'est la ligne MOSI, ce qui signifie que nous ne pouvons rien compter de l'écran, ce que la fiche technique nous dit.

image

Tout d'abord, nous allons configurer SPI, pour cela, nous allons attaquer SPI1 et GPIO, configurer les jambes de SDA et SCK comme une fonction alternative (j'ai aussi fait MISO, mais ce n'est pas nécessaire). Le mode de fonctionnement est configuré comme un émetteur maître unidirectionnel.

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; } 


Ensuite, nous écrivons une fonction de transfert d'octets simple par SPI:
 void stm32f407_spi::stm32f407_spi_send(uint8_t data) { SPI1->DR = data; while((SPI1->SR & SPI_SR_BSY)) continue; } 

Cela suffit pour que SPI fonctionne dans le mode dont nous avons besoin et transmette les données à la vitesse la plus élevée possible.

Initialisation de l'affichage


Il est maintenant temps d'initialiser l'affichage. Pour ce faire, envoyez une certaine séquence de commandes. Une commande se compose de 3 octets, où un octet est le numéro de commande et les deux autres sont des données. Vous devez également commuter la broche A0 lors de l'envoi de l'octet de commande à «0» et pour les données à «1». Pour plus de commodité, j'ai créé des fonctions en ligne pour changer l'état des broches RST, A0 et CS de l'écran.

 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;} 


Ensuite, la ligne de commande ressemblera à ceci:
 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)); } 

Pour initialiser, vous devez soumettre la séquence de commandes suivante, que j'ai espionnée dans les bibliothèques Arduino pour cet écran:

Commandes d'initialisation
 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 }, }; 


où TFT_DELAY signifie sommeil simple la quantité spécifiée de mS. Si vous plongez dans la fiche technique, de telles données pour certaines adresses sembleront étranges, car de nombreuses adresses écrivent sur des bits réservés.

Pour initialiser, laissez «0» à CS, redémarrez l'affichage (broche RST) et parcourez le tableau des commandes.

  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); 

Après cela, l'image devrait changer l'image d'interférence de télévision de couleur blanche en couleur grise.

image

Dessinez un rectangle


Le contrôleur SSD1283A vous permet de dessiner des images avec des rectangles, pour lesquels 4 commandes sont utilisées. La commande 0x44 contient les coordonnées de la fin et du début du rectangle le long de l'axe des abscisses dans l'octet de données haut et bas, respectivement. La commande 0x45 est la même pour l'axe des ordonnées. La commande 0x21 contient les coordonnées du rendu initial, dans l'octet haut pour y, dans l'octet bas pour x. La commande 0x22 contient la couleur du pixel actuel. Cela signifie qu'il doit être répété pour chaque pixel du rectangle actuel. L'affichage a également une fonctionnalité, bien qu'il ait lui-même une résolution de 130x130, sa grille de coordonnées virtuelle a des dimensions de 132x132, et les coordonnées commencent à compter à partir du point 2x2.

Ainsi, si, par exemple, nous voulons dessiner un carré noir 20 par 20, dont le point de départ est à la position (30, 45), alors nous devons transmettre la séquence de commandes suivante:

0x44 0x3320 (30 + 20 + 2-1, 30 + 2)
0x45 0x422F (45 + 20 + 2-1, 45 + 2)
0x21 0x2F20
0x22 0x0000, et cette commande doit être transmise 400 (20 * 20) fois.

Ensuite, la fonction de dessin du rectangle ressemblera à ceci (à condition que les coordonnées soient déjà décalées de 2):

 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); } } 

Pour dessiner un rectangle, spécifiez simplement les coordonnées de ses coins et sa couleur. Un exemple de remplissage de l'écran entier avec du rose ressemblera à ceci:

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

Résultat:

image

Dessinez des lettres et des chiffres


Créez une liste de tableaux avec les coordonnées des personnages.

Tableaux de coordonnées de lettres et de chiffres
 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}}; } 


Créez un tableau où nous corrélons le personnage lui-même (en ascii), le nombre de ses rectangles et ses coordonnées:

Table de pointeur de coordonnées
 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)} }; 


Ensuite, les fonctions de dessin d'un caractère ressembleront à ceci:

 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); } } } 

La fonction draw_char représente une machine à états finis, où une boucle for parcourt chaque champ de la table de coordonnées et recherche une correspondance par symbole. Si une correspondance est trouvée, les données sont transférées vers la fonction draw_lit, qui dessine le nombre souhaité de rectangles aux coordonnées données.

Le dessin au trait ressemblera à ceci:
 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++; } } 

Maintenant, définissez simplement la ligne, sa position initiale et sa couleur. Pour ajouter de nouvelles lettres, créez simplement un tableau de coordonnées et ajoutez-le au tableau. Un exemple de lignes aléatoires par 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); 

image

Dans ce mode, l'affichage est rapide, l'œil ne remarque pas le processus de dessin.

Pour tous les intéressés, le code complet du projet peut être consulté sur le lien .

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


All Articles