Разбираемся с LCD экраном LPH9157-2 от Siemens C75/ME75

d00d06ff3110477eafbe1a1a628d2617.jpgВнятной документации на этот экран я не нашел поэтому пришлось разбираться с тем что есть и экспериментировать. В качестве управляющего устройства я использовал Raspberry PI. Так-же была написана программа позволяющая превратить этот экран в мини-монитор.ОписаниеДанный дисплей имеет разрешение 135×176 пикселей и даёт возможность работать с тремя цветовыми палитрами 16(6–5–6), 12(4–4–4) и 8(3–3–2) бит.Распиновка и подключение Тут всё просто, экран питается напряжением 2,9 вольт, подсветка (LED±) запитывается отдельно напряжением примерно 12 вольт (я использовал батарею аккумуляторов соединённую с подсветкой через резистор на 510 Ом).Pin description # Name Function 1 RS Low=CMD, High=DATA 2 ~RST Reset input, active low 3 ~CS Chip select, active low 4 SYNC External frame synchorization input, unused by default 5 CLK SPI Clock-in signal (High-to-Low) 6 DATA SPI Data-in signal (MSB first) 7 VCC Power supply, normally 2.9V (I tested with 3.3V) 8 GND Ground 9 LED+ Backlight voltage, approx. 12V (depends on required current) 10 LED- Backlight common pin Как можно заметить экран управляется через интерфейс SPI (контакты CS/CLK/DAT (MOSI)), предположительно это лишь половина интерфейса так как отсутствует контакт MISO, следовательно писать данные в экран мы можем, а вот читать — нет (здесь следует упомянуть что SPI может работать в двунаправленном режиме с использованием одного провода (MIMO), но так как отсутствует какие либо команды чтения данных из экрана будем считать что этот режим экраном не используется).И перед тем как переходить непосредственно к управлению экраном надо бы этот экран к чему-нибудь подключить. В моём случае это будет Raspberry Pi. Контакты SPI экрана подключены к соответствующим им контактам SPI «малины», RS и RST к GPIO_17 и GPIO_27 соответственно. Данное подключение актуально для RPI Revision-2, если у вас иная модель то названия и номера контактов GPIO могут отличаться.

aea56675e0ea4f5cb5742772ebedd2e0.jpg 3146ca8f3b804714b8ef6c710b1c4687.png Заморачиваться с разъёмом подключения экрана я не стал и просто подпаялся к выводам проводом МГТФ. Экран в данном подключении питается от 3.3В, а не от 2.9 как в описании.Вот так выглядит вся схема в сборе7439aa05a4b742ee900160ed1503bcd5.jpgКоманды управления экраном Экран управляется достаточно просто — путём посылки по SPI команд и данных. Отличить одни от других экрану помогает состояние пина RS где высокий уровень (лог. 1) означает передачу данных, а низкий (лог. 0) передачу команд. При передаче используется тупоконечный (big-ending) порядок байт.Список команд:

CMD_RESET 0×01 — программный сброс CMD_MEMORY_ACCESS_CONTROL 0×36 — установка направления заполнения области дисплея, имеет однобайтовый аргумент 0bVHRXXXXX, гдеV — заполнение по вертикали (0 — сверху-вниз, 1 — снизу-вверх), H — заполнение по горизонтали (0 — слева-направо, 1 — справа-налево), R — меняются местами строки и столбцы (при этом заполнение остается сверху-вниз, слева-направо)) CMD_WAKEUP 0×11 — выход из спящего режима CMD_PALETTE 0×3A — установка цветовой палитры 8(0×02), 12(0×03) и 16(0×05) бит CMD_ENABLE 0×29 — включение дисплея CMD_SET_X 0×2A — задаем область рисования по X CMD_SET_Y 0×2B — задаем область рисования по Y CMD_START_WRITE 0×2C — начало записи в видеопамять Код Работа экрана была проверена во всех 3х цветовых режимах но, дабы не захламлять исходник, далее я буду рассматривать только 16-битный. Во всех остальных режимах работа экрана не отличается, за исключением, разве что 12-битного — в нём на 2 пикселя приходится 3 байта, а если нужно вывести лишь одну точку то посылается 2 байта (4 младших бита последнего экраном игнорируются).Для доступа к GPIO «малины» была использована библиотека bcm2835.Начальная инициализация GPIO int init_gpio () { if (! bcm2835_init ()) return 0;

bcm2835_spi_begin (); bcm2835_spi_setBitOrder (BCM2835_SPI_BIT_ORDER_MSBFIRST);

// CPOL = 0, CPHA = 0, Clock idle low, data is clocked in on rising edge, output data (change) on falling edge bcm2835_spi_setDataMode (BCM2835_SPI_MODE0);

// в телефоне экран работает на частоте SPI в 13 МГц // поэтому небольшое превышение по частоте ему не повредит // хотя у меня он продолжал работать и при вдвое большей частоте (30 МГц) bcm2835_spi_setClockDivider (BCM2835_SPI_CLOCK_DIVIDER_16); ///< 16 = 64ns = 15.625MHz bcm2835_spi_chipSelect(BCM2835_SPI_CS0); /// Select Our Device bcm2835_spi_setChipSelectPolarity(BCM2835_SPI_CS0, LOW);

// настроим порты на запись bcm2835_gpio_fsel (LCD_RS, BCM2835_GPIO_FSEL_OUTP); bcm2835_gpio_fsel (LCD_RESET, BCM2835_GPIO_FSEL_OUTP);

return 1; } Несколько вспомогательных функций void send_cmd (char cmd) { bcm2835_gpio_write (LCD_RS, RS_CMD); // следующий байт — команда bcm2835_spi_transfer (cmd); }

void send_data (char data) { bcm2835_gpio_write (LCD_RS, RS_DATA); // следующий байт — данные bcm2835_spi_transfer (data); } Задание области рисования void set_draw_area (char x1, char y1, char x2, char y2) { send_cmd (CMD_SET_X); send_data (x1); send_data (x2);

send_cmd (CMD_SET_Y); send_data (y1); send_data (y2); } В результате экспериментов с экраном выяснилось что необязательно задавать область перед выводом каждого кадра, достаточно задать её лишь единожды и послав команду начала записи просто гнать по SPI последовательность кадров непрерывным потоком.Подготовка к выводу и передача данных void draw_start () { send_cmd (CMD_START_WRITE); bcm2835_gpio_write (LCD_RS, RS_DATA); }

void send_draw_data (char* data, int size) { bcm2835_spi_transfern (data, size); } В процессе инициализации экрана обнаружилась досадная вещь — для запуска необходимо передёрнуть RESET экрана, а вот последующие манипуляции данным выводом приводят экран в ступор и он перестаёт реагировать на внешние воздействия. Приходится сбрасывать его по питанию. Это необходимо учесть при разработке программы дабы процедура аппаратного сброса выполнялась лишь единожды.Инициализация экрана void reset_LCD () { // аппаратный сброс bcm2835_gpio_write (LCD_RESET, LOW); bcm2835_delay (50); bcm2835_gpio_write (LCD_RESET, HIGH); bcm2835_delay (50);

// программный сброс send_cmd (CMD_RESET); }

void init_LCD () { reset_LCD ();

send_cmd (CMD_MEMORY_ACCESS_CONTROL); send_data (0b00000000);

send_cmd (CMD_WAKEUP);

bcm2835_delay (20);

send_cmd (CMD_PALETTE); send_data (_16_BIT_COLOR);

bcm2835_delay (20);

send_cmd (CMD_ENABLE); } Это была подготовка, а теперь самое вкусное — попробуем вывести на экран 16-битную картинку. Первый блин как известно — комом, после запуска программы я получил довольно странное изображение, оказалось — неверный порядок байт, после исправления всё заработало.Код int main (int argc, char **argv) { if (! init_gpio ()) return 1;

init_LCD ();

set_draw_area (0, 0, SCREEN_WIDTH — 1, SCREEN_HEIGHT — 1); draw_start ();

uint16_t screen[SCREEN_HEIGHT][SCREEN_WIDTH];

FILE* f_scr = fopen («test.bmp», «r»); fseek (f_scr, 0×42, SEEK_SET); // skip bmp header

fread (&screen, 1, SCREEN_HEIGHT * SCREEN_WIDTH * 2/*16bit*/, f_scr); fclose (f_scr);

// change byte order for (int x = 0; x < SCREEN_WIDTH; x++) for(int y = 0; y < SCREEN_HEIGHT; y++) screen[y][x] = (screen[y][x] >> 8) | (screen[y][x] << 8);

send_draw_data ((char*)&screen[0][0], SCREEN_WIDTH*SCREEN_HEIGHT*2/*16 bit*/);

close_gpio (); return 0; } изображение до и после правок dd44f85264a040b5b0902f76a9952903.jpgLCD как монитор С самого начала экспериментов меня не покидала мысль использовать экран как монитор для «малины», что я и поспешил реализовать.Идея проста — изображение берётся из /dev/fb0, оно там как раз 16-битное, ресайзится и выдаётся на экран.Так как результат сжатия картинки 1024×768 => 176×132 малоинформативен, для фреймбуфера было установлено разрешение 320×240, это можно сделать правкой config.txt на FAT разделе «малиновой» флешки. framebuffer_width=320 framebuffer_height=240 После этого изображение всё равно жмётся с использованием примитивной интерполяции, но результат уже можно назвать приемлемым. Так-же был добавлен пропуск одинаковых кадров для экономии CPU.Исходник LPH9157–2_RPI.c #include #include #include #include

#include #include #include

// соответствия контактов GPIO и LCD #define LCD_RS RPI_V2_GPIO_P1_11 #define LCD_RESET RPI_V2_GPIO_P1_13

#define RS_CMD 0 #define RS_DATA 1

#define CMD_RESET 0×01 #define CMD_MEMORY_ACCESS_CONTROL 0×36 // Memory Access Control #define CMD_WAKEUP 0×11 // Выход из спящего режима #define CMD_PALETTE 0×3A // Установка цветовой палитры #define CMD_ENABLE 0×29 //Включение дисплея

#define CMD_SET_X 0×2A // задаем область по X #define CMD_SET_Y 0×2B // задаем область по Y #define CMD_START_WRITE 0×2C // начало записи в память

#define _8_BIT_COLOR 0×02 #define _12_BIT_COLOR 0×03 #define _16_BIT_COLOR 0×05

#define SCREEN_WIDTH 132 #define SCREEN_HEIGHT 176

int init_gpio () { if (! bcm2835_init ()) return 0;

bcm2835_spi_begin (); bcm2835_spi_setBitOrder (BCM2835_SPI_BIT_ORDER_MSBFIRST); bcm2835_spi_setDataMode (BCM2835_SPI_MODE0); /// CPOL = 0, CPHA = 0, Clock idle low, /// data is clocked in on rising edge, /// output data (change) on falling edge bcm2835_spi_setClockDivider (BCM2835_SPI_CLOCK_DIVIDER_16); ///< 16 = 64ns = 15.625MHz bcm2835_spi_chipSelect(BCM2835_SPI_CS0); /// Select Our Device bcm2835_spi_setChipSelectPolarity(BCM2835_SPI_CS0, LOW);

// настроим порты на запись bcm2835_gpio_fsel (LCD_RS, BCM2835_GPIO_FSEL_OUTP); bcm2835_gpio_fsel (LCD_RESET, BCM2835_GPIO_FSEL_OUTP); return 1; }

int close_gpio () { bcm2835_spi_end (); bcm2835_close (); }

void send_cmd (char cmd) { bcm2835_gpio_write (LCD_RS, RS_CMD); bcm2835_spi_transfer (cmd); }

void send_data (char data) { bcm2835_gpio_write (LCD_RS, RS_DATA); bcm2835_spi_transfer (data); }

void send_data_array (char* data, int size) { bcm2835_gpio_write (LCD_RS, RS_DATA); bcm2835_spi_transfern (data, size); }

void set_draw_area (char x1, char y1, char x2, char y2) { send_cmd (CMD_SET_X); send_data (x1); send_data (x2);

send_cmd (CMD_SET_Y); send_data (y1); send_data (y2); }

void draw_start () { send_cmd (CMD_START_WRITE); bcm2835_gpio_write (LCD_RS, RS_DATA); }

void send_draw_data (char* data, int size) { bcm2835_spi_transfern (data, size); }

void reset_LCD () { bcm2835_gpio_write (LCD_RESET, LOW); bcm2835_delay (50); bcm2835_gpio_write (LCD_RESET, HIGH); bcm2835_delay (50);

send_cmd (CMD_RESET); }

void init_LCD () { reset_LCD ();

send_cmd (CMD_MEMORY_ACCESS_CONTROL); send_data (0b00000000);

send_cmd (CMD_WAKEUP);

bcm2835_delay (20);

send_cmd (CMD_PALETTE); send_data (_16_BIT_COLOR);

bcm2835_delay (20);

send_cmd (CMD_ENABLE); }

#define FB_WIDTH 320// 176 #define FB_HEIGHT 240// 144

int main (int argc, char **argv) { if (! init_gpio ()) return 1;

int smooth = 0; int dynamic_fps = 0; int argn = 1; while (argn < argc) { if(argv[argn][0] == '-') switch(argv[argn][1]) { case 'i': init_LCD(); close_gpio(); printf("lcd initialized\n"); return 0; break;

case 's': smooth = 1; break; case 'd': dynamic_fps = 1; break; default: printf («Usage: lcd [options]\n»); printf («Options:\n»); printf (» -i initialize lcd (hardware reset)\n»); printf (» -d dynamic FPS (skip same frames)\n»); printf (» -s smooth image (enable basic intrpolation)\n»); return 0; break; } argn++; }

///------------------------------------------------ /// draw screen

set_draw_area (0, 0, SCREEN_WIDTH — 1, SCREEN_HEIGHT — 1); draw_start (); uint16_t screen[SCREEN_HEIGHT][SCREEN_WIDTH];

uint16_t old_fb[FB_HEIGHT * FB_WIDTH];

int fd_scr = open (»/dev/fb0», O_RDONLY); int scr_sz = FB_HEIGHT * FB_WIDTH * 2/*16bit*/; uint16_t* fb_screenshot = mmap (0, scr_sz, PROT_READ, MAP_PRIVATE, fd_scr, 0);

// scaling float scale_X = FB_HEIGHT / (float)SCREEN_WIDTH; float scale_Y = FB_WIDTH / (float)SCREEN_HEIGHT;

int frame_cnt = 0; struct timespec ts1, ts2; clock_gettime (CLOCK_MONOTONIC, &ts1);

for (;;) // forever { if (dynamic_fps) if (memcmp (&old_fb, fb_screenshot, sizeof (old_fb)) == 0) { usleep (10000); continue; } else { memcpy (&old_fb, fb_screenshot, sizeof (old_fb)); }

for (int x = 0; x < SCREEN_WIDTH; x++) for(int y = 0; y < SCREEN_HEIGHT; y++) { int fb_x = y * scale_X; int fb_y = x * scale_Y;

uint16_t px = fb_screenshot[fb_x + fb_y * FB_WIDTH];

if (smooth) { // look around if ((fb_x — 1 >= 0) && (fb_x + 1 < FB_WIDTH) && (fb_y - 1 >= 0) && (fb_y + 1 < FB_HEIGHT)) { for(int dx = -1; dx <= 1; dx++) for(int dy = -1; dy <= 1; dy++) if((dx == 0) ^ (dy == 0)) { uint16_t add_px = fb_screenshot[(fb_x + dx) + (fb_y + dy) * FB_WIDTH]; px = ((px & 0xf7de) >> 1) + ((add_px & 0xf7de) >> 1); /// ^thank you habr => http://habr.ru/p/128773/ } } } screen[y][SCREEN_WIDTH — 1 — x] = (px << 8) | (px >> 8); }

send_draw_data ((char*)&screen[0][0], sizeof (screen));

/// calc fps frame_cnt++; if (frame_cnt >= 100) { clock_gettime (CLOCK_MONOTONIC, &ts2);

float allsec = (ts2.tv_sec — ts1.tv_sec) + (ts2.tv_nsec — ts1.tv_nsec) / 1000000000.0; float fps = frame_cnt / allsec;

printf (»%f FPS\n», fps);

frame_cnt = 0; clock_gettime (CLOCK_MONOTONIC, &ts1); }

usleep (1000); }

munmap (fb_screenshot, scr_sz); close (fd_scr); close_gpio ();

printf («fin\n»); return 0; } Сборка: pi@raspberrypi ~ $ gcc -o lcd LPH9157–2_RPI.c -lbcm2835 -lrt -std=gnu99 Запуск: pi@raspberrypi ~ $ sudo ./lcd -i pi@raspberrypi ~ $ sudo ./lcd -d -s параметры:-i — первичная инициализация (дёргаем хардварный reset)-s — включить сглаживание-d — динамический fps (одинаковые кадры пропускаются — экономит CPU)Результат abd5cfebf58a42e9aaa5c57507d57165.jpg

© Habrahabr.ru