Oled-дисплей WEH001202
Решил попробовать oled-дисплей WEH001202. Порадовало наличие SPI-протокола. Он, конечно, поддерживает и старые протоколы типа 6800 и 8080, но это пережитки прошлого. Использование этих протоколов для новых устройств считаю моветоном (хотя и этот SPI оказался не тем, что хотелось :D).
Работу с параллельными протоколами рассматривать не будем - там всё как в подобных дисплеях. Рассмотрим работу по SPI и графический режим.
Оглавление
- Аппаратное подключение
- Особенности SPI-протокола
- Программное описание протокола
- Инициализация и настройки
- Графический режим
- Приложения
Аппаратное подключение
По умолчанию дисплей настроен на работу по параллельным протоколам. Для перевода в работу по SPI необходимо перепаять на плате дисплея две перемычки (нулевые резисторы).
На рисунке 1 показано, как было до и после доработки. Дисплеи могут отличаться расположением резисторов. На рисунке 1 (справа) привёл пример немного отличающегося дисплея, не помню, откуда взял.
Рисунок 1. Фото перемычек до после доработки и ещё один вариант исполнения такого дисплея.
Перемычки на плате подписаны. В принципе понятно, что куда.
Дорабатываем (перепаиваем), подключаем в соответствии с даташитом на дисплей.
Я подключал к плате NUCLEO-F103RB. Схему подключения изобразил на рисунке 2.
Рисунок 2. Схема подключения дисплея.
WEH001202 основан на чипе WS0010, поэтому всю информацию по работе с дисплеем будем брать из даташита на этот чип. Аппаратно реализованный SPI контроллера не получилось использовать, поэтому подключил как удобнее было (как провода легли).
Соответствие пинов разъёма дисплея и разных названий.
Pin12 - DB5 – SCL – он же SCLK – тактовый сигнал
Pin13 - DB6 – SDO – он же MISO – данные дисплея в контроллер
Pin14 - DB7 – SDI – он же MOSI – данные от контроллера в дисплей
Pin15 – NC (используется только в режиме SPI) – CS – он же SS – это выбор чипа.
Особенности SPI-протокола
В целом SPI как SPI (диаграмма приведена на рисунке 3). Ничего особенного, если не считать, что надо передавать 10 бит, а при чтении получаем 18. В общем, как ни крути, аппаратным SPI не обойтись. Не видел ещё ни одного контроллера, где подобное решение было аппаратным.
Рисунок 3. Диаграмма SPI для записи и для чтения.
Придётся дёргать ногами вручную :(
По времянке. Судя по даташиту, период клока 300 нс. Соответственно, это 3.333 Мгц – это и будет максимальный битрейт.
У меня получилась вот такая диаграмма.
Рисунок 4. Осцилограмма получившегося протокола. канал1- SCL, канал 2 – SDI
По осцилограмме видно, что она «немного» отличается от идеальной. Суть простая: вначале выставляем данные, потом дёргаем клок. На осцилограмме нет ещё одного нужного канала - выбора чипа. Он постоянно 0 во время передачи, и только после окончания передачи/приёма байта выставляется в 1.
Программное описание протокола
Для общения с дисплеем написал 3 процедурки. Не стал заморачиваться и писать универсальную процедуру с параметрами, думаю, так удобнее будет:
Вывод команды
void spi_com (uint8_t dat){ //Вывод команды в дисплей uint8_t i; //для счётчика uint16_t data=dat; //RS=0 RW=0 по этому просто приравниваем чтоб разрядность соответствовала lcd_scl_s; //SCL=1 подготовка к передаче lcd_sdi_s; //SDI=1 lcd_cs_c; //CS=0 выбор чипа (начало передачи) for (i=0;i<10;i++){ //счётчик на 10 (будем 10бит передавать) lcd_scl_c; //SCL=0 это защёлка. if (data & 0x200) lcd_sdi_s//если не ноль выставляем бит SDI=1 else lcd_sdi_c //если ноль соответственно SDI=0 lcd_scl_s; //SCL=1 это защёлка данных тут дисплей принял один бит data<<=1; //сдвигаем дату } lcd_cs_s; //CS=1 концовочка выбор чипа убираем и дисплей смотрит чё принял
Вывод данных
void spi_dat (uint8_t dat){ //Вывод данных в дисплей uint8_t i; //для счётчика uint16_t data=0x200 | dat; //добавляем бит команды RS=1 lcd_scl_s; //SCL=1 подготовка к передаче lcd_sdi_s; //SDI=1 lcd_cs_c; //CS=0 выбор чипа (начало передачи) for (i=0;i<10;i++){ //счётчик на 10 (будем 10бит передавать) lcd_scl_c; //SCL=0 это защёлка. if (data & 0x200) lcd_sdi_s//если не ноль выставляем бит SDI=1 else lcd_sdi_c //если ноль соответственно SDI=0 lcd_scl_s; //SCL=1 это защёлка данных тут дисплей принял один бит data<<=1; //сдвигаем дату } lcd_cs_s; //CS=1 концовочка выбор чипа убираем и дисплей смотрит чё принял }
Чтение статуса
uint8_t spi_in (){ //Чтение из дисплея uint8_t i; //для счётчика uint8_t in; //для приёма данных uint16_t data=0x100; //при приёме надо передавать 10 бит //в них сигналы RS RW и 8бит пустых данных //RS=0 RW=1 (чтение) 8бит данных 0x00 uint16_t bites=0x200; //это масочка для проверки битов на вывод lcd_scl_s; //SCL=1 подготовка к передаче lcd_sdi_s; //SDI=1 lcd_cs_c; //CS=0 выбор чипа (начало передачи) for (i=0;i<18;i++){ //приём состоит из 18бит первые 10 это просто команда на чтение //следующие 8бит это данные lcd_scl_c; //готовим клок if (data & bites) lcd_sdi_s//если не ноль выставляем бит SDI=1 else lcd_sdi_c; //если ноль соответственно SDI=0 if (bites) bites>>=1; //если не прокрутился полностью крутим else bites=0x200; //если прокрутился то возврящаем на 10бит in<<=1; //приёмный байт поворачиваем для подготовки нового бита if (lcd_sdo) in|=1; //приписываем принятый бит lcd_scl_s; //защёлкиваем клок } lcd_cs_s; //CS=1 концовочка выбор чипа убираем и дисплей смотрит чё принял return in; //выплёвываем принятый байт }
Пины назначаются с помощью дефайнов в начале программы.
Писал пример под STM32, поэтому получилось вот так:
#define lcd_scl_s GPIOA->BSRR = GPIO_BSRR_BS0; //клок #define lcd_scl_c GPIOA->BSRR = GPIO_BSRR_BR0; #define lcd_sdo GPIOA->IDR & GPIO_IDR_IDR1 //прин прима данных с дисплея PORTA (1бит) #define lcd_sdi_s GPIOA->BSRR = GPIO_BSRR_BS4; //вывод данных в контроллер #define lcd_sdi_c GPIOA->BSRR = GPIO_BSRR_BR4; #define lcd_cs_s GPIOB->BSRR = GPIO_BSRR_BS0; //выбор чипа #define lcd_cs_c GPIOB->BSRR = GPIO_BSRR_BR0;
Для работы процедур необходимо подправить дефайны под свою схему, контроллер или что там ещё можно поменять.
По скорости особо не заморачивался - написал как написалось.тактовая контроллера была 8Мгц. Тактовая протокола получилась (можно посмотреть на рис. 4) 100кГц - в принципе, нормально для большинства случаев.
Инициализация и настройки
Для инициализации надо послать в дисплей 7 байт с настройками. Это минимальный набор, достаточный для включения и работы дисплея. Сейчас побитно все байты разберём:
- 0 0 1 1 N F FT1 FT0
- 0 0 0 0 1 D C B
- 0 0 0 0 0 0 0 1 - команда очистки дисплея.
- 0 0 0 0 0 0 1 0 – перевод курсора в начало дисплея.
- 0 0 0 0 0 1 I/D SH
- 0 0 0 1 G/C PWR 1
- 0 0 0 1 S/C R/L 0 0 - настройки режима.
N – количество строк в дисплее 0 – однострочный, 1 - двухстрочный
F – размер шрифта. В даташите написано, что шрифт может быть 5х8 и 5х10. Эксперименты показали, что изменения шрифта не происходит. Меняется положение курсора. Он становится на второй строке, где-то ближе к середине. Я думаю, что это для отдельных каких-то вариантов дисплея.
FT1, FT0 – это переключение набора символов. Там их 4. В третьем наборе есть русские символы.
D – включение-выключение дисплея; 1-включен.
C – включение-выключение курсора; 1-включен. Мигает весь символ.
B – подсветка позиции курсора; 1-включен. Палка под символом.
I/D – смещение курсора, увеличение/уменьшение.
SH – включение сдвига.
1 - выбор режима ну и включение какого-то внутреннего источника питания
C/G – выбор режима; 1-графический 0-текстовый
PWR – включение внутреннего источника. Всегда 1, в общем (если не надо спячки).
S/C – выбор: или дисплей сдвигается при выводе , или курсор. 0 – значит, курсор.
R/L – выбор: сдвиг вправо или влево.
Для инициализации дисплея сделана небольшая процедура:
Инициализация дисплея в текстовом режиме с включеной подсветкой знакоместа (для наглядности), что получилось смотрите на рис.5 а
void lcd_init (){ // процедура инициализации дисплея lcd_scl_s; lcd_sdi_s; lcd_cs_s; //ноги вверх ! //выдаём дисплею 5 заветных байт lcd_wait_BF(); //ждём готовности на всякий случай spi_com (0x38); //N-1 F-0 FT1,0-0 т.е дисплей 2строчный , шрифт 5х8, набр символов 0 lcd_wait_BF(); //ждём готовности spi_com (0x0E); //D-1 C-0 B-1 т.е дисплей Вкл. Курсор Вкл. Знакоместо - подсвечивается lcd_wait_BF(); //ждём готовности spi_com (0x01); // это не команда очистки дисплея не путать ! lcd_wait_BF(); //ждём готовности spi_com (0x02); //возврат в начало lcd_wait_BF(); //ждём готовности spi_com (0x06); //I/D-1 SH-0 т.е сдвиг курсора вправо при выводе, скрол экрана выключен //тут настройка закончена можно перелючать режимы spi_com (0x17); //настройка смещения курсора в право spi_com (0x14); //включение текстового режима }
Вот ещё несколько настроек:
Настроечные байты по порядку: 0x38, 0x0E, 0x01, 0x02, 0x05, 0x17, 0x14 (см. рисунок 5.б).
Задано смещение дисплея, а не курсора (как в предыдущем варианте). НО ! когда дисплей смещается как бы в отрицательную сторону, курсор переходит на другую строку - в общем, надо там хитрить, чтоб курсор не уходил в минус, или учитывать это смещение. Или изначально уже писать символы в смещённое пространство и там уже двигать.
Причём вообще работать в таком режиме не очень удобно потому, что точное положение, в котором сейчас находится дисплей, непонятно. Нельзя узнать, насколько в данный момент сдвинут дисплей. Можно прочитать только текущее положение курсора (вместе со статусом). Поэтому, если всё сглючит (у меня было такое), контроллер думает, что всё порядке, а изображение всё сдвинуто. И никак это не проконтролировать. Выкручивался тем, что инициализировал дисплей время от времени.
Настроечные байты по порядку: 0x3C, 0x0E, 0x01, 0x02, 0x06, 0x17, 0x14 рисунок 5.в
В этой настройке выбрал шрифт не 5х8, а 5х10. Увеличения шрифта не произошло! Но непонятно, зачем курсор идёт по нижнему ряду посередине почти - вроде как ненужный режим.
Рисунок 5. Опыты с настройками.
Помимо трёх наборов символов WEH001202 позволяет (собственно, как и большинство подобных дисплеев) подгрузить произвольные символы. Где-то натыкался, мол, в этом дисплее, в отличии от других, подгружаются 16 символов. Нет. Их тут тоже 8.
Для подгрузки написана вот такая процедурка:
void load_sym (const uint8_t *pnt, uint8_t num,uint8_t count){ //num-это порядковый номер символа (0-7) начиная //с которого будем подгружать символы //count – количество символов spi_com (0x40+(num<<3)); //тут мы выдаём в дисплей адрес CGRAM по которому символ //хранится. 0x40-команда +номер символа *8. for (uint8_t i=0;i<(count*8);i++){ //и цикл с передачей данных spi_dat (*pnt++); } }
Применяется это так:
Создаём массив, в котором хранятся нарисованные символы. Я их формировал с помощью программульки Symbols8x5 - про неё попозже напишу.
Примеры программы. Всё тут выписывать не буду, результат на рис 6
Рисунок 6. Вывод подгруженных дополнительных символов.
С обычным символьным режимом всё понятно.
Самое интересное - это графический режим.
Графический режим
Для перевода дисплея в графический режим достаточно установить бит C/G, не обязательно при этом делать всю инициализацию. Можно переключать режимы прямо на лету: типо spi_com (0x1F) и всё, дисплей переходит в графический режим, потом опа - spi_com (0x17) - и он переключается обратно.
Если закрасить дисплей полностью, получится вот что:
Рисунок 7.Разделение на символы в графическом режиме.
По рисунку видно, что полностью дисплей не закрасился.
Вкратце по командам:
Для указания на адрес, грубо говоря, по оси Х - команда 0x80. Чтобы переместить курсор на нужную позицию, надо выдать команду spi_com(0x80 + X); где Х – это смещение соответственно. В пикселях слева направо.
Для перемещения курсора по вертикали выдаём команду spi_com(0x40+Y), где Y = 0 - верхняя строка, Y = 1 -нижняя.
Любая запись данных (spi_dat) выдаёт 8 бит на дисплей. Младшие биты вверху, старшие внизу.
Для демонстрации создал программульку небольшую, выводящую логотип. Таблицу логотипа сгенерировал с помощью программы logos_8 (потом про неё тоже расскажу - делал её для преобразования картинок в массив, удобовставляемый в код). Вот результат.
Рисунок 8. Пример работы с графическим дисплеем.
Самое хорошее в графическом дисплее то, что нет привязанности к шрифтам. Можно подгружать любые символы, сделать нормальную удобоваримую таблицу для работы с русскими шрифтами, чтобы не хранить строки в каких-то непонятных символах, а в нормальной раскладке.
Для этого есть у меня программулька, которая преобразует картинку в шрифт; очень удобно редактировать и вставлять в код. Сейчас сделаем шрифт, подгрузим и посмотрим. Программа называется fonts5x8.
Весь текст проекта смотреть не будем (там ничего особенного), разберём только некоторые моменты.
В начале программы вставляем таблицу с шрифтом – например, вот такой (нарисовал по-быстрому, особо не заморачиваясь с дизайном. Обычный шрифт)
static const uint8_t sym[]={ 0,0,0,0,0,//0 0,0,191,0,0,//1 0,6,0,6,0,//2 40,124,40,124,40,//3 …… ….. 252,32,120,132,120,//158 216,36,36,252,0,//159 };
Получилось 160 символов. По 5 байт, итого 800байт на шрифт :) круто конечно, но когда памяти полно, можно раскошелиться - зато преобразование при выводе минимальное. Накидал вот такую процедурку для вывода символов. Всё красиво.
void print_sym (uint8_t sy){//вывод одного символа uint8_t ddt; if (sy>0xAF) sy=sy-64; //это смещение т.к в раскладке Русские буквы смещены sy=sy-32; //это смещение т.к. чтоб отбросить управляющие символы for (uint8_t q=0;q<5;q++){ //вывод 5 байт. Т.е прорисовка символа ddt=sym[sy*5+q]; spi_dat(ddt); } }
Сначала проверяем, если это русские буквы строчные, то смещаем их на 64. Если посмотреть раскладку ascii-кодов, это будет понятно, так как там видно, что они стоят отдельно и между ними и остальными буквами ненужные символы. Вооот. Потом вычитаем 32 - это чтобы убрать управляющие символы, которых в таблице нет, и вуаля!
Потом добавляем процедурку вывода строки
void print_str(uint8_t *dat){ //выводим строку символов while (*dat) { print_sym(*dat++); } }
Тоже всё просто, даже нечего объяснять - обычный вывод. Зато можно не задумываясь писать print_str(«бла бла бла”) - дисплей всё поймёт. Пример кода здесь. Результат на рисунке 9.
Рисунок 9.Результат вывода собственного шрифта в графическом режиме.
Ну вот и всё, собственно. Напоследок бегущая строчка а-ля спектрум :) не хватает ещё снежка на заднем фоне :) музыку взял из «Shock Megademo Better Quality Part 2 ZX Spectrum».
Приложения
- Даташит на дисплей WEH001202
- Даташит на чип дисплея WS0010
- Программа преобразования для создания доп. символов symbol8x5
- Пример кода с подгрузкой доп. символов
- Пример кода с загрузкой и выводом логотипа
- Программа для преобразования картинок в спрайты logos_8
- Программа для преобразования картинки в шрифт fontes8x5
- Пример кода с выводом на дисплей символов в графическом режиме
- Пример кода с выводом на дисплей бегущей строки
Программы для компа написаны в Processing. Программы для контроллеров написаны в IAR 7.50
Автор: Сергей Меньшиков , e-mail для связи -