Inicie Transflective TFT Display en SSD1283A con STM32

Introduccion


El modelo de pantalla se llama H016IT01. Esta pantalla es interesante principalmente porque es transflectiva. Esto significa que la imagen debe ser visible incluso bajo el sol brillante. Y también este es casi el único modelo disponible con esta característica en el famoso sitio chino.

Pero el artículo fue publicado porque hay muy poca información sobre el controlador SSD1283A (tanto en los segmentos ruso como occidental de la red), y no he visto una guía en ningún lado. Puede encontrar una hoja de datos en la red, pero no hay información sobre la inicialización y el trabajo con la pantalla, y solo son útiles las descripciones de los registros.

Quiero enfatizar que este material ciertamente no es la verdad última. Solo doy mi experiencia interactuando con el dispositivo. El objetivo principal del artículo es simple: ayudar a todos aquellos que decidan, quieran o quieran trabajar con esta pantalla, nada más.

imagen

La pantalla tiene 8 pines:

1) GND
2) VCC - 5 o 3.3V
3) CS - Selección de chip SPI
4) RST - "0" - apaga la pantalla, "1" - se enciende.
5) A0 / DC - Comando de datos ("0" - comando, "1" - datos)
6) SDA - SPI MOSI
7) SCK - SPI SCK
8) LED - salida de luz de fondo, como VCC, de 3.3 a 5V

Necesita programar la pantalla usando SPI, la placa de descubrimiento en stm32f407 me ayudará con esto.

SPI


Aunque interactué con SSD1283A a través de SPI, vale la pena señalar que el controlador también proporciona una interfaz paralela, pero este modelo de pantalla no lo admite. Su SPI tampoco es ordinario, solo tiene una línea de datos SDA. De hecho, esta es la línea MOSI, lo que significa que no podemos contar nada desde la pantalla, que nos dice la hoja de datos.

imagen

Primero configuraremos SPI, para esto atacaremos SPI1 y GPIO, configuraremos las patas de SDA y SCK como una función alternativa (también hice MISO, pero esto no es necesario). El modo de operación está configurado como un transmisor maestro unidireccional.

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


Luego escribimos una función de transferencia de bytes simple por SPI:
 void stm32f407_spi::stm32f407_spi_send(uint8_t data) { SPI1->DR = data; while((SPI1->SR & SPI_SR_BSY)) continue; } 

Esto es suficiente para que SPI funcione en el modo que necesitamos y transmita datos a la mayor velocidad posible.

Visualización de inicialización


Ahora es el momento de inicializar la pantalla. Para hacer esto, envíe una cierta secuencia de comandos. Un comando consta de 3 bytes, donde un byte es el número de comando y los otros dos son datos. También debe cambiar el pin A0 cuando envíe el byte de comando a "0" y los datos a "1". Para mayor comodidad, hice funciones en línea para cambiar el estado de los pines RST, A0 y CS de la pantalla.

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


Entonces la línea de comando se verá así:
 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, debe enviar la siguiente secuencia de comandos, que vi en las bibliotecas de arduino para esta pantalla:

Comandos de inicialización
 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 }, }; 


donde TFT_DELAY significa sueño simple la cantidad especificada de mS. Si profundiza en la hoja de datos, dichos datos para algunas direcciones parecerán extraños, ya que muchas direcciones escriben en bits reservados.

Para inicializar, deje “0” en CS, reinicie la pantalla (pin RST) y revise la tabla 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); 

Después de eso, la imagen debería cambiar la imagen de interferencia de televisión de color blanco a gris.

imagen

Dibujar un rectángulo


El controlador SSD1283A le permite dibujar imágenes con rectángulos, para los cuales se utilizan 4 comandos. El comando 0x44 contiene la coordenada del final y el comienzo del rectángulo a lo largo del eje de abscisas en el byte de datos alto y bajo, respectivamente. El comando 0x45 es el mismo para el eje de ordenadas. El comando 0x21 contiene la coordenada de la representación inicial, en el byte alto para y, en el byte bajo para x. El comando 0x22 contiene el color para el píxel actual. Esto significa que debe repetirse para cada píxel del rectángulo actual. La pantalla también tiene una función, aunque tiene una resolución de 130x130, su cuadrícula de coordenadas virtual tiene dimensiones de 132x132, y las coordenadas comienzan a contar desde el punto 2x2.

Por lo tanto, si, por ejemplo, queremos dibujar un cuadrado negro de 20 por 20, cuyo punto de partida esté en la posición (30, 45), entonces debemos transmitir la siguiente secuencia de comandos:

0x44 0x3320 (30 + 20 + 2-1, 30 + 2)
0x45 0x422F (45 + 20 + 2-1, 45 + 2)
0x21 0x2F20
0x22 0x0000, y este comando debe transmitirse 400 (20 * 20) veces.

Entonces la función de dibujo del rectángulo se verá así (siempre que las coordenadas ya estén desplazadas 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 dibujar un rectángulo, solo especifique las coordenadas de sus esquinas y color. Un ejemplo de llenar toda la pantalla con rosa se verá así:

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

Resultado:

imagen

Dibuja letras y números


Crea una lista de matrices con coordenadas para los personajes.

Arreglos de coordenadas de letras y 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}}; } 


Cree una tabla donde correlacionemos el propio carácter (en ascii), el número de sus rectángulos y sus coordenadas:

Tabla de puntero coordinado
 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)} }; 


Entonces las funciones de dibujo de un personaje se verán así:

 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 función draw_char representa una máquina de estados finitos, donde un bucle for pasa por cada campo de la tabla de coordenadas y busca una coincidencia por símbolo. Si se encuentra una coincidencia, los datos se transfieren a la función draw_lit, que dibuja el número deseado de rectángulos en las coordenadas dadas.

El dibujo lineal se verá así:
 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++; } } 

Ahora es suficiente establecer la línea, su posición inicial y el color. Para agregar nuevas letras, simplemente cree una matriz de coordenadas y agréguela a la tabla. Un ejemplo de líneas aleatorias 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); 

imagen

En este modo, la pantalla es rápida, el ojo no nota el proceso de dibujo.

Para todos los interesados, el código completo del proyecto se puede ver en el enlace .

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


All Articles