Введение
Модель дисплея называется H016IT01. Данный дисплей интересен прежде всего тем, что он является транфлективным(transflective). Это означает, что изображение на нем должно быть видно даже под ярким солнцем. А также это чуть ли не единственная доступная модель с этой особенностью на известном китайском сайте.
Статья же увидела свет потому, что информации по контроллеру SSD1283A очень мало(как в русском, так и западном сегменте сети), и руководства я нигде не встречал. В сети можно найти даташит, однако там нет информации по инициализации и работе с дисплеем, а из полезного только описания регистров.
Хочу подчеркнуть, что данный материал конечно же не является истиной последней инстанции. Я привожу лишь свой опыт взаимодействия с устройством. Основная цель статьи проста — помочь всем тем, кто решил, хочет или захочет поработать с данным дисплеем, не более.
1) GND
2) VCC — 5 или 3.3V
3) CS — SPI Chip Select
4) RST — «0» — выключает дисплей, «1» — включает.
5) A0/DC — Data Command(«0» — команда, «1» — данные)
6) SDA — SPI MOSI
7) SCK — SPI SCK
8) LED — вывод подцветки, как и VCC, от 3.3 до 5V
Программировать дисплей нужно по SPI, в этом мне поможет плата discovery на stm32f407.
SPI
Хоть я и взаимодействовал с SSD1283A по SPI, стоит заметить, что контроллером предусмотрен и параллельный интерфейс, но данная модель дисплея его не поддерживает. SPI у него тоже не обычный, на нем всего одна линия данных SDA. По сути, это линия MOSI, а значит с дисплея мы ничего считать не можем, что нам и говорит даташит.
Для начала настроим SPI, для этого затактируем SPI1 и GPIO, настроим ноги SDA и SCK как альтернативную функцию(MISO я тоже сделал, но это не обязательно). Режим работы настраиваем как однонаправленный передатчик мастер.
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; }
Затем напишем простую функцию передачи байта по SPI:
void stm32f407_spi::stm32f407_spi_send(uint8_t data) { SPI1->DR = data; while((SPI1->SR & SPI_SR_BSY)) continue; }
Этого достаточно чтобы SPI заработал в нужном нам режиме и передавал данные на максимально возможной скорости.
Инициализация дисплея
Теперь самое время инициализировать дисплей. Для этого нужно отправить определенную последовательность команд. Команда состоит из 3 байт, где один байт — номер команды, а два других — данные. Также нужно переключать пин A0 при посылке байта команды в «0», а при данных в «1». Для удобства я сделал inline функции для переключения состояния пинов RST, A0 и CS дисплея.
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;}
Тогда посылка команды будет выглядеть так:
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)); }
Для инициализации нужно подать следующую последовательность команд, которую я подсмотрел в ардуино библиотеках для данного экрана:
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 }, };
где TFT_DELAY означает простой Sleep указанное количество mS. Если покопаться в даташите, такие данные для некоторых адресов покажутся странными, поскольку по многим адресам запись идёт в зарезервированные биты.
Для инициализации подадим «0» на CS, перезагрузим дисплей(пин RST), и пройдёмся по таблице команд.
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);
После этого на дисплее должно поменяться изображение с белого на серый цвет телевизионных помех.
Рисуем прямоугольник
Контроллер SSD1283A позволяет рисовать изображения прямоугольниками, для чего используются 4 команды. Команда 0x44 содержит координату конца и начала прямоугольника по оси абсцисс в старшем и младшем байте данных соответственно. Команда 0x45 есть тоже самое для оси ординат. Команда 0x21 содержит координату начальной отрисовки, в старшем байте для y, в младшем для x. Команда 0x22 содержит цвет для текущего пикселя. Это означает что её нужно повторять для каждого пикселя текущего прямоугольника. Также у дисплея есть особенность, хоть сам он и обладает разрешением 130×130, его виртуальная координатная сетка имеет размеры 132×132, а координаты начинают отсчёт с точки 2×2.
Таким образом, если мы, например, хотим нарисовать квадрат чёрного цвета 20 на 20, начальная точка которого находится в позиции (30, 45) то нужно передавать следующую последовательность команд:
0x44 0x3320 (30+20+2-1, 30+2)
0x45 0x422F (45+20+2-1, 45+2)
0x21 0x2F20
0x22 0x0000, причем эту команду нужно передать 400(20*20) раз.
Тогда функция отрисовки прямоугольника будет выглядеть так(при условии, что координаты уже сдвинуты на 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); } }
Для отрисовки прямоугольника достаточно указать координаты его углов и цвет. Пример заливки всего экрана розовым будет выглядеть так:
t_rect rect = {0x02, 0x02, 0x84, 0x84, static_cast<uint16_t>(COLOUR::MAGENTA)}; this->draw_rect(rect);
Результат:
Рисуем буквы и цифры
Создадим перечень массивов с координатами для символов.
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}}; }
Создадим таблицу, где соотнесем сам символ(в ascii), количество его прямоугольников и его координаты:
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)} };
Тогда функции отрисовки одного символа будут выглядеть вот так:
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); } } }
В функции draw_char представлен конечный автомат, где цикл for проходит по каждому полю таблицы координат, и ищет совпадение по символу. При найденном совпадении данные передаются в функцию draw_lit, которая отрисовывает нужное количество прямоугольников по заданным координатам.
Отрисовка строки будет выглядеть следующим образом:
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++; } }
Теперь достаточно задать строку, её начальное положение и цвет. Для добавления новых букв достаточно создать массив координат и добавить его в таблицу. Пример случайных строк по коду:
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);
В таком режиме дисплей работает быстро, глаз не замечает процесса отрисовки.
Для всех интересующихся, полный код проекта можно посмотреть по ссылке.
ссылка на оригинал статьи https://habr.com/ru/post/482610/
Добавить комментарий