В начало | Зарегистрироваться | Заказать наши киты почтой
 
 
 
 

Визуализация для микроконтроллера. Часть 1. OLED дисплей 0.96" (128х64) на SSD1306

📆24 августа 2016   ✒️erbol   🔎90.182   💬20  
Добрый день, друзья!
Эта статья открывает цикл, посвящённый средствам визуального отображения информации.
Нами будут рассмотрены модули, в состав которых входят следующие дисплеи и управляющие контроллеры:
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

Тема сегодняшней нашей беседы – вот такой модуль:

Основные характеристики 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 байт в каждой. В случае настроек по сбросу/подаче питания данные из ОЗУ выводятся на дисплей в следующем порядке:
Визуализация для микроконтроллера. Часть 1. OLED дисплей 0.96" (128х64) на SSD1306

А комбинация команд 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

Пожалуй, на этом всё, конец первой части.
Продолжение следует!

Камрад, рассмотри датагорские рекомендации

🌼 Полезные и проверенные железяки, можно брать

Опробовано в лаборатории редакции или читателями.




 

Читательское голосование

Нравится

Статью одобрили 104 читателя.

Для участия в голосовании зарегистрируйтесь и войдите на сайт с вашими логином и паролем.

14 сентября 2016 изменил Datagor. Добавлен архив с переводом таблиц команд.

 

Поделись с друзьями!

 

 

Связанные материалы

 

Схема на Датагоре. Новая статья Визуализация для микроконтроллера. Часть 2. TFT дисплей 1.8" (128х160) на ST7735... Следующий из рассматриваемых нами модулей обладает полноцветным дисплеем под управлением...
Схема на Датагоре. Новая статья Визуализация для микроконтроллера. Часть 3. TFT дисплей 2.8" (240х320) на ILI9341... Битва за урожай закончена, можно продолжить повествование. Полноцветный TFT-дисплей 240×320 ILI9341...
Схема на Датагоре. Новая статья Визуализация для микроконтроллера. Часть 4. Android... Вообще то я планировал рассказать сегодня про дисплей на базе ILI9481. Однако, он настолько похож...
Схема на Датагоре. Новая статья Программная реализация протокола SPI на AVR в CodeVisionAVR... Всем коллегам и согражданам привет! Увлёкся я изучением протоколов. Про реализацию протокола I2C у...
Схема на Датагоре. Новая статья Ремонт и модернизация мультиметра UNI-T UT60E. Цветной TFT-дисплей, литиевый аккумулятор... Всем датагорцам и гостям нашего кибер-города привет! :bye: В статье я повествую о том, как я...
Схема на Датагоре. Новая статья Беспроводной канал связи 2,4 ГГц на базе трансивера nRF24L01+ от Nordic Semiconductor. Часть 1... Доброго вам дня, уважаемые граждане и гости Датагор.ру - этого замечательного сообщества увлечённых...
Схема на Датагоре. Новая статья Elect_60: программа микроконтроллерного управления внешними устройствами от ПК... Многие наши коллеги желающие создать микроконтроллерное устройство, управляемое от ПК сталкиваются...
Схема на Датагоре. Новая статья Программная реализация протокола I2C на AVR в CodeVisionAVR... Пару лет назад, изучая Atmega8, я захотел программно реализовать работу с устройствами на шине...
Схема на Датагоре. Новая статья Проект DMX512. Микроконтроллер управляет профессиональным шоу. Ч.1... DMX512 (англ. Digital Multiplex) - стандарт, описывающий метод цифровой передачи данных между...
Схема на Датагоре. Новая статья Вторая жизнь лампового радиоприемника Philips 592LN (Голландия, 1947). Часть 5... В этой части статьи речь пойдет: — о предварительном усилителе и его питании, — о питании модуля...
Схема на Датагоре. Новая статья Вторая жизнь лампового радиоприемника Philips 592LN (Голландия, 1947). Часть 4. Модуль BlueTooth OVC3860... Привет, датагорцы! В этой части моего повествования речь пойдёт о модуле BlueTooth OVC3860 (далее...
Схема на Датагоре. Новая статья Несколько функций для програмной реализации протокола I2C на AVR... Добрый день, дорогие друзья! Решил поделиться с вами несколькими функция для работы по протоколу...
 

Комментарии, вопросы, ответы, дополнения, отзывы

 

Назад Вперед
<
Читатель Датагора

erbol

<
Читатель Датагора

Андрей



<
Читатель Датагора

ivanbrash

<
Читатель Датагора

erbol

Назад Вперед

Добавить комментарий, вопрос, отзыв 💬

Камрады, будьте дружелюбны, соблюдайте правила!

  • Смайлы и люди
    Животные и природа
    Еда и напитки
    Активность
    Путешествия и места
    Предметы
    Символы
    Флаги
 
 
В начало | Зарегистрироваться | Заказать наши киты почтой