Lançamento da tela Transflective TFT no SSD1283A com STM32

1. Introdução


O modelo de exibição é chamado H016IT01. Essa exibição é interessante principalmente porque é transfletiva. Isso significa que a imagem nela deve ser visível mesmo sob o sol forte. E também este é quase o único modelo disponível com esse recurso no famoso site chinês.

Mas o artigo foi lançado porque há muito pouca informação no controlador SSD1283A (nos segmentos russo e ocidental da rede) e não vi um guia em lugar algum. Você pode encontrar uma folha de dados na rede, mas não há informações sobre como inicializar e trabalhar com a tela, e apenas descrições de registros são úteis.

Quero enfatizar que esse material certamente não é a verdade suprema. Dou apenas minha experiência de interação com o dispositivo. O objetivo principal do artigo é simples - ajudar todos aqueles que decidem, querem ou desejam trabalhar com essa exibição, não mais.

imagem

A tela possui 8 pinos:

1) GND
2) VCC - 5 ou 3.3V
3) Seleção de Chip CS - SPI
4) RST - “0” - desliga a tela, “1” - liga.
5) A0 / DC - Comando de Dados (“0” - comando, “1” - dados)
6) SDA - SPI MOSI
7) SCK - SPI SCK
8) LED - saída de luz de fundo, como VCC, de 3,3 a 5V

Você precisa programar a exibição usando SPI, a placa de descoberta no stm32f407 me ajudará com isso.

SPI


Embora eu tenha interagido com o SSD1283A via SPI, é importante notar que o controlador também fornece uma interface paralela, mas esse modelo de exibição não é compatível. Seu SPI também não é comum, possui apenas uma linha de dados SDA. De fato, esta é a linha MOSI, o que significa que não podemos contar nada no visor, o que a folha de dados está nos dizendo.

imagem

Primeiro, configuraremos o SPI; para isso, atacaremos o SPI1 e o GPIO, configuraremos as pernas do SDA e do SCK como uma função alternativa (também fiz o MISO, mas isso não é necessário). O modo de operação é configurado como um mestre transmissor unidirecional.

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


Em seguida, escrevemos uma simples função de transferência de bytes da SPI:
 void stm32f407_spi::stm32f407_spi_send(uint8_t data) { SPI1->DR = data; while((SPI1->SR & SPI_SR_BSY)) continue; } 

Isso é suficiente para que o SPI funcione no modo que precisamos e transmita dados na velocidade mais alta possível.

Inicialização de exibição


Agora é a hora de inicializar a exibição. Para fazer isso, envie uma certa sequência de comandos. Um comando consiste em 3 bytes, em que um byte é o número do comando e os outros dois são dados. Você também precisa alternar o pino A0 ao enviar o byte de comando para "0" e os dados para "1". Por conveniência, criei funções em linha para mudar o estado dos pinos RST, A0 e CS da tela.

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


Em seguida, a linha de comando ficará assim:
 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)); } 

Para inicializar, você precisa enviar a seguinte sequência de comandos, que espiei nas bibliotecas do arduino para esta tela:

Comandos de inicialização
 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 }, }; 


onde TFT_DELAY significa simples Repouso a quantidade especificada de mS. Se você se aprofundar na folha de dados, esses dados para alguns endereços parecerão estranhos, pois muitos endereços gravam em bits reservados.

Para inicializar, deixe “0” para CS, reinicie a tela (pino RST) e siga a tabela de comandos.

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

Depois disso, a imagem deve mudar a imagem de branco para cinza na interferência da televisão.

imagem

Desenhe um retângulo


O controlador SSD1283A permite desenhar imagens com retângulos, para os quais são utilizados 4 comandos. O comando 0x44 contém a coordenada do final e do início do retângulo ao longo do eixo da abcissa no byte de dados alto e baixo, respectivamente. O comando 0x45 é o mesmo para o eixo de ordenadas. O comando 0x21 contém a coordenada da renderização inicial, no byte alto para y, no byte baixo para x. O comando 0x22 contém a cor do pixel atual. Isso significa que ele precisa ser repetido para cada pixel do retângulo atual. A tela também possui um recurso, embora ela própria tenha uma resolução de 130x130, sua grade de coordenadas virtual tem dimensões de 132x132 e as coordenadas começam a contar a partir do ponto 2x2.

Assim, se, por exemplo, queremos desenhar um quadrado preto de 20 por 20, cujo ponto de partida está na posição (30, 45), precisamos transmitir a seguinte sequência de comandos:

0x44 0x3320 (30 + 20 + 2-1, 30 + 2)
0x45 0x422F (45 + 20 + 2-1, 45 + 2)
0x21 0x2F20
0x22 0x0000, e este comando deve ser transmitido 400 (20 * 20) vezes.

Em seguida, a função de desenho do retângulo terá a seguinte aparência (desde que as coordenadas já sejam deslocadas por 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); } } 

Para desenhar um retângulo, basta especificar as coordenadas de seus cantos e cores. Um exemplo de preenchimento da tela inteira com rosa ficará assim:

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

Resultado:

imagem

Desenhe letras e números


Crie uma lista de matrizes com coordenadas para os caracteres.

Matrizes de coordenadas de letras e números
 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}}; } 


Crie uma tabela na qual correlacionemos o próprio caractere (em ascii), o número de seus retângulos e suas coordenadas:

Tabela de ponteiro de coordenadas
 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)} }; 


Em seguida, as funções de desenho de um caractere ficarão assim:

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

A função draw_char representa uma máquina de estados finitos, onde um loop for passa por cada campo da tabela de coordenadas e procura uma correspondência por símbolo. Se uma correspondência for encontrada, os dados são transferidos para a função draw_lit, que desenha o número desejado de retângulos nas coordenadas fornecidas.

O desenho da linha ficará assim:
 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++; } } 

Agora basta definir a linha, sua posição inicial e cor. Para adicionar novas letras, basta criar uma matriz de coordenadas e adicioná-la à tabela. Um exemplo de linhas aleatórias por código:

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

imagem

Nesse modo, a exibição é rápida, o olho não percebe o processo de desenho.

Para todos os interessados, o código completo do projeto pode ser visualizado no link .

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


All Articles