Эта статья открывает цикл, посвящённый средствам визуального отображения информации.
Нами будут рассмотрены модули, в состав которых входят следующие дисплеи и управляющие контроллеры:
1. OLED дисплей на 0.96 дюйма (128×64 пикселей), контроллер SSD1306.
2. TFT дисплей на 1.8 дюйма (128×160 пикселей), контроллер ST7735.
3. TFT дисплей на 2.8 дюйма (240×320 пикселей), контроллер ILI9341.
4. TFT дисплей на 3.5 дюйма (320×480 пикселей), контроллер ILI9481.
Весь материал разбит на пять частей, в четырёх из которых изложена информация по модулям (технические параметры, подключение и инициализация, вывод на дисплей базового графического элемента — точки), а пятая содержит единую графическую библиотеку для описанных дисплеев.
Сама идея единой библиотеки и большинство приёмов её реализации позаимствованы из библиотеки Adafruit_GFX, а также отдельных работ её авторов — Лимор Фрид (Limor Fried) и Кевина Таунсенда (Kevin Townsend).
Моя скромная заслуга заключается в переносе кода с С++ на С, написании отдельных функций и выборочном переводе даташитов с целью более детального освещения особенностей работы модулей.
OLED дисплей на 0.96 дюйма (128×64 пикселей), контроллер SSD1306
Тема сегодняшней нашей беседы – вот такой модуль:Содержание статьи / Table Of Contents
↑ Основные характеристики OLED-модуля
• Размеры: 27мм х 27мм х 4.1 мм.
• Разрешение: 128х64 пикселя.
• Угол обзора: >160°.
• Цветность: чёрно-белый. В реальности «белый» пиксель может светиться голубым или, как на рисунке выше, жёлтым цветом, но это, как вы понимаете, не делает дисплей цветным.
• Энергопотребление: 0.08Вт при включённом дисплее.
• Рабочая температура: от – 30 до 70 градусов по Цельсию.
• Напряжение питания: 3В – 5В.
• Протокол обмена данными: I2c или SPI.
↑ Назначение выводов
Для модуля с протоколом I2c:• SCL, SDA – линии протокола. Подтягивающие резисторы уже встроены в модуль, поэтому соединение с микроконтроллером (МК) – напрямую.
• VCC, GND – линии питания.
Для модуля с протоколом SPI:
• SCK, MOSI – линии протокола. Тут каждый производитель старался, как мог, поэтому эти выводы могут иметь и другие названия – DO/DI, CLK/DIN и даже, как ни странно, SCL/SDA. Вывод MISO отсутствует по причине, о которой будет сказано ниже.
• CS – выбор чипа, аналог SS. Активный уровень – низкий.
• DC – выбор типа записываемого в SSD1306 слова – данные или команда.
• RES – аппаратный сброс. В прилагаемой библиотеке прописана функция программного сброса, так что, если вы ограничены в выводах микроконтроллера, можно соединить этот вывод с аналогичным пином МК либо подтянуть его к питанию.
• VCC, GND – линии питания.
↑ Особенности протоколов обмена данными
Протокол SPI – стандартный, за исключением двух нюансов:1. Чтение из модуля невозможно по причине отсутствия вывода MISO. Причина, по которой производители пошли на это, заключается, как мне кажется, в следующем. Единственная информация, которую можно извлечь из SSD1306 – включен дисплей или нет. Данные, согласитесь, не настолько бесценные, чтобы тратиться на организацию целой линии обмена.
2. В зависимости от состояния вывода DC записываемый в модуль байт будет интерпретироваться как команда (состояние «0») или данные (состояние «1»).
Функция записи команды, с учётом макроопределений, будет выглядеть так:
#define cbi(_byte, _bit) _byte &= ~(1<<_bit)
#define sbi(_byte, _bit) _byte |= (1<<_bit)
void ssd1306_command(uint8_t command)
{
cbi(SSD_PORT, DC_pin);
cbi(SSD_PORT, CS_pin);
SPI_byte(command);
sbi(SSD_PORT, CS_pin);
}
А байт данных записывается следующим образом:
void ssd1306_data(uint8_t data)
{
sbi(SSD_PORT, DC_pin);
cbi(SSD_PORT, CS_pin);
SPI_byte(data);
sbi(SSD_PORT, CS_pin);
}
Для выяснения особенностей протокола I2c обратимся к следующему рисунку из прилагаемого даташита SSD1306:
Как видите, запись в модуль происходит как минимум тремя байтами.
Первый байт – сдвинутый на один бит влево адрес (0х3С) с добавленным битом выбора типа операции (чтение или запись). В интернете упоминается, что адрес SSD1306 может быть и 0x3D, поэтому, если ваш модуль не запускается, возможно, стоит попробовать поменять в программе его адрес.
Следующим идёт контрольный байт, шестой бит которого (D/C) определяет тип записываемого слова: команда или данные. По сути, этот бит – программный аналог вывода DC в модулях с протоколом SPI.
Третий байт – непосредственно команда или данные.
Таким образом, функции обмена обретают следующий вид:
#define SSD1306_ADDRESS 0x3C
#define DC_BIT 6
#define CONTROL_BYTE_COMMAND 0 << DC_BIT
#define CONTROL_BYTE_DATA 1 << DC_BIT
#define WRITE 0
void ssd1306_command(uint8_t command)
{
i2c_start();
i2c_write((SSD1306_ADDRESS << 1) | WRITE);
i2c_write(CONTROL_BYTE_COMMAND);
i2c_write(command);
i2c_stop();
}
void ssd1306_data(uint8_t data)
{
i2c_start();
i2c_write((SSD1306_ADDRESS << 1) | WRITE);
i2c_write(CONTROL_BYTE_DATA);
i2c_write(data);
i2c_stop();
}
Функция чтения для протокола I2c в библиотеке не прописана по соображениям, приведённым выше для объяснения отсутствия вывода MISO.
↑ Система команд
Полный перевод таблиц команд выложен в архиве. Все команды условно разделены на шесть групп:1. Базовые команды.
2. Команды прокрутки.
3. Команды адресации.
4. Команды аппаратной настройки.
5. Команды тайминга и схемы управления.
6. Команды ёмкостного умножителя.
Команды могут быть как однобайтными так и многобайтными, причем второй и последующие байты записываются как отдельные команды:
ssd1306_command(command_byte_1);
ssd1306_command(command_byte_2);
ssd1306_command(command_byte_3);
Информация в переведённых таблицах изложена достаточно подробно и полно, поэтому остановлюсь на наиболее важных командах, которые нам пригодятся.
0хAE/0хAF – выключение/включение дисплея. Как только модуль подключается к питанию, SSD1306 готов принимать и исполнять команды, в том числе выводить изображение на дисплей. Однако само изображение вы не увидите, поскольку по сбросу/подаче питания выполняется команда 0хAE, т.е. дисплей выключен. Поэтому в функции инициализации модуля необходимо, среди прочего, прописать команду 0хAF.
0х81 – двухбайтная команда установки контрастности, значение которой определяется вторым байтом в диапазоне от 1 до 256.
0х20 – установка режима адресации. Эта команда определяет порядок вывода данных на дисплей: горизонтальный, вертикальный или постраничный. Более подробно об этом - чуть позже, а пока лишь скажу, что мы будем использовать режим горизонтальной адресации.
0х21/0х22 – установка адресов столбца/страницы. В более понятном выражении эти команды позволяют задать координаты пикселя, который вы хотите активировать.
А0/А1 - с точки зрения изображения на дисплее эти команды ответственны за отражение по горизонтали.
C0/C8 – определение порядка сканирования COM-выводов дисплея. Если проще – отражение по вертикали.
Сочетание двух последних пар команд позволяет регулировать ориентацию изображения.
0x8D - включение/выключение ёмкостного умножителя. При включении дисплея без использования этой команды, изображение будет едва видимо, даже, если командой 0х81 выставить максимальное значение контрастности. Дело в том, что рабочее напряжение дисплея – от 7В до 15В. Поскольку подаваемого на выводы модуля напряжения (3В – 5В) не достаточно, в его состав включён емкостной умножитель, повышающий уровень напряжения до необходимого. По сбросу/подаче питания этот узел отключен, поэтому второй обязательной командой в функции инициализации модуля должна быть, помимо 0хAF, команда включения ёмкостного умножителя.
↑ Вывод изображения на дисплей
Собственно, как только вывод DC (протокол SPI) или бит D/C контрольного байта (протокол I2c) выставлены в 1, записываемый в модуль байт будет выводиться на дисплей. Если быть более точным, мы с помощью функции ssd1306_data() отправляем байт в ОЗУ (GDDRAM), входящего в состав SSD1306, а далее данные автоматически выводятся на дисплей.Давайте разберёмся, как именно происходит этот процесс.
В соответствии с количеством пикселей на дисплее ОЗУ содержит 128х64 бит, разделенных на восемь страниц по 128х8 бит или 128 байт в каждой. В случае настроек по сбросу/подаче питания данные из ОЗУ выводятся на дисплей в следующем порядке:
А комбинация команд 0хА1 и 0хС8 меняет порядок вывода таким образом:
Каждому из 128 байтов в странице ОЗУ соответствует вертикальная черта шириной 1 и высотой 8 пикселей в области дисплея, ограниченной на рисунке прямоугольником с соответствующей надписью. Отсчёт адреса столбцов ведётся слева-направо, а порядок расположения битов байта – сверху-вниз. Более наглядно сказанное поясняет рисунок из даташита:
Здесь выполнены следующие действия: соответствующими командами заданы столбец 4 (SEG4) и страница 2 (PAGE2), а затем с записано число 01000001 (65 – в десятичном исчислении). Часть кода, обеспечивающая этот процесс, выглядит для режимов горизонтальной или вертикальной адресации так:
//записываем команду установки адреса столбца
ssd1306_command(0х21);
//начальный адрес столбца
ssd1306_command(4);
//конечный адрес столбца, в данном случае он совпадает с начальным
ssd1306_command(4);
//записываем команду установки адреса страницы
ssd1306_command(0х22);
//начальный адрес страницы
ssd1306_command(2);
//конечный адрес страницы, в данном случае он совпадает с начальным
ssd1306_command(2);
//записываем число 65
ssd1306_data(65);
По исполнению вы получите два светящихся пикселя в заданном месте дисплея:
На примере записи одного байта данных трудно уловить смысл понятий «режим адресации», «начальный адрес» и «конечный адрес», а они будут играть в дальнейшем очень важную роль. Поэтому, рассмотрим вариант последовательной записи нескольких байтов подряд.
В состав SSD1306 входят счётчики адресов столбцов и страниц, текущее значение которых и определяет адрес в ОЗУ (читай – координаты на дисплее), по которому будет записан байт данных. Каждое обращение к GDDRAM приводит к автоматическому инкременту того или иного счётчика, а заданные нами начальный и конечный адреса определяют, начиная с какого и до какого значения будет происходить счёт.
Выбирая же режим адресации, мы определяем порядок работы и взаимодействия счётчиков.
• Для режима горизонтальной адресации при каждом исполнении функции ssd1306_data() увеличивается на единицу значение счётчика адреса столбца. По достижении заданного конечного значения счётчик адреса столбца сбрасывается в начальное значение, а счётчик адреса страницы увеличивается на единицу. После достижения счётчиком адреса страницы заданного конечного значения оба счётчика сбросятся в начальные значения и процесс пойдёт по второму кругу.
• Для режима вертикальной адресации, наоборот, увеличивается на 1 значение счётчика адреса страницы. После его заполнения следует сброс в начальное значение, инкремент счётчика адреса столбца и так далее.
• Для режима постраничной адресации предусмотрен только счётчик адреса столбца, то есть все действия происходят в пределах одной выбранной страницы. Кроме того, в этом режиме не предусмотрен конечный адрес столбца, так что рост значения счётчика продолжается до значения 127 (конец страницы), а затем, в отличие от двух других режимов, не сбрасывается в начальное значение, а обнуляется.
Для наглядности изложенного приведу части кода и полученные на дисплее результаты для всех трёх режимов адресации.
#define HORIZONTAL_ADDRESSING 0x00
// выбираем режим горизонтальной адресации
ssd1306_command(0x20);
ssd1306_command(HORIZONTAL_ADDRESSING);
// пределы счёта для счётчика столбцов – от 64 до 127
ssd1306_command(0x21);
ssd1306_command(64);
ssd1306_command(127);
// пределы счёта для счётчика страниц – от 3 до 4
ssd1306_command(0x22);
ssd1306_command(3);
ssd1306_command(4);
// выводим 65 вертикальных чёрточек по 8 пикселей на дисплей
for(int i = 0; i < 65; i++)
{
ssd1306_data(255);
}
#define VERTICAL_ADDRESSING 0x01
// выбираем режим вертикальной адресации
ssd1306_command(0x20);
ssd1306_command(VERTICAL_ADDRESSING);
// пределы счёта для счётчика столбцов – от 64 до 65
ssd1306_command(0x21);
ssd1306_command(64);
ssd1306_command(65);
// пределы счёта для счётчика страниц – от 3 до 7
ssd1306_command(0x22);
ssd1306_command(3);
ssd1306_command(7);
// выводим 6 вертикальных чёрточек по 8 пикселей на дисплей
for(int i = 0; i < 6; i++)
{
ssd1306_data(255);
}
#define PAGE_ADDRESSING 0x02
#define COLUMN_NUMBER 64
#define PAGE_NUMBER 3
// выбираем режим постраничной адресации
ssd1306_command(0x20);
ssd1306_command(PAGE_ADDRESSING);
// младшая тетрада начального адреса столбца
ssd1306_command(0x00 | (COLUMN_NUMBER & 0x0F));
// старшая тетрада начального адреса столбца
ssd1306_command(0x10 | (COLUMN_NUMBER >> 4));
//начальный адрес страницы
ssd1306_command(0xB0 | PAGE_NUMBER);
// выводим 65 вертикальных чёрточек по 8 пикселей на дисплей
for(int i = 0; i < 65; i++)
{
ssd1306_data(255);
}
Перейдём к прорисовке базового элемента – точки. Как вы поняли, конструктивные особенности SSD1306 не дают нам возможность задавать отдельную строку на дисплее, а лишь страницу. Во-первых, из-за этого мы не можем объединить единой библиотекой этот модуль с другими, которые оперируют координатами пикселя - столбец/строка (или – х/у). Во-вторых, это – просто неудобно и непривычно.
Используем следующие конструкции для определения из координаты «у» номера страницы:
uint8_t page;
if(y < 8)
{
page = 0;
}
else if (y >= 8)
{
page = (int)(y/8);
}
и строки:
y&7
Тогда вывести точку в нужное место дисплея можно так:
#define BLACK 0
#define WHITE 1
//х – от 0 до 127, у – от 0 до 63
void draw_pixel(uint8_t x, uint8_t y, uint8_t color)
{
uint8_t page;
if(y < 8)
{
page = 0;
}
else if (y >= 8)
{
page = (int)(y/8);
}
ssd1306_command(0x21);
ssd1306_command(x);
ssd1306_command(x);
ssd1306_command(0x22);
ssd1306_command(page);
ssd1306_command(page);
if(color == WHITE)
{
ssd1306_data(1 << (y&7));
}
else if(color == BLACK)
{
ssd1306_data(0 << (y&7));
}
}
На этом статью можно было бы считать завершённой, если бы не одно «но» - мы не можем поменять состояние одного пикселя, не затронув остальные семь, связанные с ним. Используя функцию draw_pixel() в приведённом виде мы будем гасить на дисплее 7 ни в чём не повинных точек.
Чтобы этого не происходило, необходимо не только каким-то образом изменить функцию, но и, до вхождения в неё, знать состояние пикселей – 0 или 1. В масштабе всего дисплея мы должны в любой момент знать текущее состояние всех 128х64 пикселей или 128х8 байт памяти. Поскольку извлечь эту информацию из SSD1306, как вы помните, невозможно, придётся создать массив соответствующей размерности.
#define SSD1306_LCDWIDTH 128
#define SSD1306_LCDHEIGHT 64
uint8_t buffer[SSD1306_LCDWIDTH * SSD1306_LCDHEIGHT/8];
Создание массива порождает новый вопрос – как оптимально, с точки зрения кода, увязать порядковый номер элемента массива с координатами 8-пиксельной черты на дисплее? Создатели библиотеки Adafruit_GFX использовали простой и изящный приём, приняв во внимание следующее:
1. При режиме горизонтальной адресации сдвиг координат на дисплее происходит слева-направо и сверху-вниз, так же, как и при последовательной выборке элементов из массива.
2. Размеры 128х64 пикселя позволяют незаметно для человеческого глаза прорисовывать каждый раз весь дисплей.
В итоге прорисовку точки будут обеспечивать две функции:
//эта функция меняет состояние соответствующего бита
//в элементе массива, определяемого координатами х/у
void draw_pixel(int16_t x, int16_t y, uint16_t color)
{
switch (color)
{
case WHITE: buffer[x+ (y/8)*SSD1306_LCDWIDTH] |= (1 << (y&7)); break;
case BLACK: buffer[x+ (y/8)*SSD1306_LCDWIDTH] &= ~(1 << (y&7)); break;
}
}
//а эта последовательно выбирает байты из массива и отправляет их в ОЗУ SSD1306
void ssd1306_draw_display(void)
{
ssd1306_command(SSD1306_COLUMNADDR);
ssd1306_command(0);
ssd1306_command(SSD1306_LCDWIDTH-1);
ssd1306_command(SSD1306_PAGEADDR);
ssd1306_command(0);
ssd1306_command(7);
#if defined I2C_INTERFACE
i2c_start();
i2c_write((SSD1306_ADDRESS << 1) | WRITE);
i2c_write(CONTROL_BYTE_DATA);
for (uint16_t i=0; i<(SSD1306_LCDWIDTH*SSD1306_LCDHEIGHT/8); i++)
{
i2c_write(buffer[i]);
}
i2c_stop();
#elif defined SPI_INTERFACE
sbi(SSD_PORT, DC_pin);
cbi(SSD_PORT, CS_pin);
for (uint16_t i=0; i<(SSD1306_LCDWIDTH*SSD1306_LCDHEIGHT/8); i++)
{
SPI_byte(buffer[i]);
}
sbi(SSD_PORT, CS_pin);
#endif
}
↑ Структура и содержание библиотеки SSD1306
Библиотека состоит из 8 файлов. Приведу их краткое содержание:1. main.c – подключает необходимые для работы библиотеки, инициализирует модуль и выводит на дисплей изображение. Какое именно, вы узнаете, запустив программу.
2. main.h – выбор протокола обмена (SPI или I2c) и типа сброса (программный или аппаратный). Библиотека пригодна к использованию для модулей с обоими вариантами протокола и сброса, поэтому для выбора подходящих вам параметров раскомментируйте соответствующие строки в этом файле.
3. i2c.c – содержит функции программного протокола I2c. Обычно я пользуюсь аппаратным вариантом. Но, хотелось, чтобы библиотека была полезна как можно большему кругу людей, поэтому я воспользовался кодом из этой статьи Avinash Gupta. Вы можете заменить содержимое этого файла тем кодом, которому больше доверяете и обычно используете, обеспечив лишь соответствие названий функций.
4. i2c.h – назначение выводов МК, участвующих в протоколе I2c.
5. spi.c – функции программного SPI. Так же, как и в случае с I2c, можете заменить их на свои.
6. spi.h – назначение выводом МК, к которым подключается модуль с одноимённым протоколом обмена.
7. ssd1306.с – функции, непосредственно обеспечивающие работу модуля. Кроме тех, что уже упомянуты в статье, здесь вы найдёте функции инициализации, аппаратного и программного сброса, полной очистки дисплея, выбора ориентации изображения на экране, а также, для желающих организовать бегущую строку, функции прокрутки.
8. ssd1306.h – определение параметров дисплея (цвета, размеры), макроопределения всех используемых команд контроллера. Здесь же объявлен массив buffer[], который развёрнут по причине, указанной в п.1.
↑ Файлы
Релиз 2. 14-09-2016 обновлена библиотека. В main.c подключение фалов библиотеки не через h-файлы, как было, а через с-файлы, как принято в большинстве компиляторов. Кроме того явно прописано подключение стандартной библиотеки string.h🎁ssd1306_kod.7z 3.33 Kb ⇣ 373
Релиз 1:
🎁kod.7z 3.22 Kb ⇣ 294
Перевод на русский таблиц команд SSD130:
🎁perevod-tablic-komand-ssd1306-datagor.ru.7z 58.05 Kb ⇣ 306
Даташит на контроллер дисплея SSD1306:
🎁ssd1306_datashit.7z 1.19 Mb ⇣ 251
Пожалуй, на этом всё, конец первой части.
Продолжение следует!
Камрад, рассмотри датагорские рекомендации
🌼 Полезные и проверенные железяки, можно брать
Опробовано в лаборатории редакции или читателями.