I2c + SH1106 с использованием только wire.h

Всем добра!

Мне нужно разобраться в протоколе дисплея, вот такого


(к сожалению, пока не на руках, жду доставку)

В планах на него использование собственного шрифта и, очень вероятно - реализация транслитерации, т.е. будет функция, которая заменит коды символы латиницы на коды символов кириллицы (в т.ч. сочетания sh/ch на ш/ч). А уже при выводе на дисплей будет браться соответствующая битмапка символа кириллицы.

Кроме того, у дисплея 8 строк вывода, высотой 8 пикселей каждая (1 байт). А для моих нужд лучше выводить 7 строчек текста, но более разряженным межстрочным интервалом, примерно так:
image
(пока только латиницу отрисовал, если честно, начертания в интернете экспроприировал)

Таким образом, существующие популярные библиотеки мне не совсем подходят…

Как правильно хранить шрифт, и как это правильно позиционировать вопросов нет. Я не знаю, как правильно формировать команды для чипа дисплея. Datasheet прочел дважды, но то ли знания английского не хватает, то ли ума (одно другому не мешает, согласен). Т.е. начало, по всей видимости, такое

#include <Wire.h>

void setup () {
  Wire.begin();
}

void loop () {
  Wire.beginTransmission(i2caddr);
  // здесь нужно отправить дисплею нужный порядок бит
  // согласно его страничной раскройке
  // вероятно в цикле использовать метод
  //  Wire.write();
  // указав 1 (или более?) байт в скобках
  Wire.endTransmission(); 
}

Из документации во-первых понятно, что я могу настроить яркость, а также что у экрана есть ОЗУ и как будто нужно в нее сначала записывать (читать тоже можно, но мне неинтересно) и потом еще отправлять команду на вывод содержимого ОЗУ непосредственно на экран. Ткните носом, пожалуйста, что и в каком порядке нужно отправлять в порт.

По-моему у U8G2 шрифтов этих… Валом. И добавить можно.

1 лайк

@Resin , не пытайтесь сделать все сразу, начните с того, что можете сделать сразу:
возьмите подходящую библиотеку, добейтесь работы одного из идущих в комплекте с библиотекой примеров, а потом поочередно вводите в него по одной своей задумке до тех пор, пока не добьетесь всего, чего хотите.
Основное - делайте то, ЧТО ЗНАЕТЕ, КАК СДЕЛАТЬ.

2 лайка

О спасиб, открыл либу Гивера, а внутри вроде даже +/- понятно. Отправка парой команда-значение, нашел уже передачу адресов страницы и столбца, вкл/выкл, настройки яркости и другие, менее мне интересные. Видимо где-то будет передача команды на запись в память конкретного байта (выбрали заранее страницу и столбец и потом пишем), ну я так полагаю, дома вчитаюсь.

// отправить команду
void sendCommand(uint8_t cmd1) {
  Wire.beginTransmission(_address);
  Wire.write(OLED_ONE_COMMAND_MODE);
  Wire.write(cmd1);
  Wire.endTransmission();
}

// отправить код команды и команду
void sendCommand(uint8_t cmd1, uint8_t cmd2) {
  Wire.beginTransmission(_address);
  Wire.write(OLED_COMMAND_MODE);
  Wire.write(cmd1);
  Wire.write(cmd2);
  Wire.endTransmission();
}

примерно вот такие функции для обмена данными в библиотеке. Вопрос. А если у меня вызов таких несколько раз подряд, мне необходимо вызывать endTransmission и beginTransmission, или можно вызвать последовательность Wire.write всех необходимых параметров, а потом закрыть сессию передачи данных?

ДА

1 лайк

На команды в I2C устройство - отдельные транзакции, с вероятностью в 90%.
На данные - одна транзакция. Если устройство умеет инкрементировать адрес ячейки.
В даташите должен быть специальный раздел “обмен по i2c”, в котором картинками разные варианты обмена показаны.

1 лайк

Да простит меня автор библиотеки, пока такое получилось. Все вспомогательные ф-ии пока что упразднил, чтобы увидеть код целиком и понять что к чему… Я могу пережить незнание назначения некоторых параметров инициализации, принять что вот такие они есть.

Но интересует команда в строке 67, если не ошибся - она идентична той, что при обращении к странице вызывается ниже. Так зачем она нужна перед циклом?

// адрес на шине, выяснить правильный, когда плата будет на руках
uint8_t _address = 0x3C;

// инициализация
void init() {
  Wire.begin();
  Wire.beginTransmission(_address);
  Wire.write(0x00); // OLED_COMMAND_MODE режим отправки команд нон-стопом
  Wire.write(0xAE); // OLED_DISPLAY_OFF выключить дисплей
  Wire.write(0xD5); // OLED_CLOCKDIV по всей видимости, делитель частоты внутренних часов, хз зачем
  Wire.write(0x80); //
  Wire.write(0x8D); // OLED_CHARGEPUMP ???
  Wire.write(0x14); //
  Wire.write(0x20); // OLED_ADDRESSING_MODE способ адресации какой-то
  Wire.write(0x01); // OLED_VERTICAL вертикальная ориентация
  Wire.write(0xA1); // OLED_NORMAL_H без отражения слева направо
  Wire.write(0xC8); // OLED_NORMAL_V без отражения сверху вниз
  Wire.write(0x81); // OLED_CONTRAST контраст
  Wire.write(0x7F); // по всей видимости величина контраста
  Wire.write(0xDB); // OLED_SETVCOMDETECT ???
  Wire.write(0x40); // 
  Wire.write(0xA6); // OLED_NORMALDISPLAY не инвертировать состояние пикселей
  Wire.write(0xAF); // OLED_DISPLAY_ON включить дисплей
  Wire.endTransmission();
  //delayMicroseconds(2);
  Wire.beginTransmission(_address);
  Wire.write(0x00); // OLED_COMMAND_MODE режим отправки команд нон-стопом 
  Wire.write(0xDA); // OLED_SETCOMPINS    не представляю, что именно, связано с разрешением.
  Wire.write(0x12); // OLED_HEIGHT_64     Возможно, какой-то режим адресации/сопоставления памяти
  Wire.write(0xA8); // OLED_SETMULTIPLEX  (чип может не знать, какая именно матрица к нему подключена)
  Wire.write(0x3F); // OLED_64
  Wire.endTransmission();
  //delayMicroseconds(2);
}

// вкл/выкл экран
void power (bool value) {
  Wire.beginTransmission(_address);
  Wire.write(0x80); // OLED_ONE_COMMAND_MODE
  Wire.write(value ? 0xAF : 0xAE); // OLED_DISPLAY_ON / OLED_DISPLAY_OFF
  Wire.endTransmission();
  // delayMicroseconds(2);
}

// яркость 0-255
void contrast (uint8_t value) {
  Wire.beginTransmission(_address);
  Wire.write(0x00); // OLED_COMMAND_MODE
  Wire.write(0x81); // OLED_CONTRAST
  Wire.write(value);
  Wire.endTransmission();
  //delayMicroseconds(2);
}

 // полностью обновить дисплей
void update() {
  
  // перевод в режим приема команд
  Wire.beginTransmission(_address);
  Wire.write(0x80); // OLED_ONE_COMMAND_MODE
  Wire.write(0x00); // OLED_COMMAND_MODE
  Wire.endTransmission();
  //delayMicroseconds(2);
  // вот это непонятно - задается какой-то адрес, хотя ниже это тоже делается???
  Wire.beginTransmission(_address);
  Wire.write(0x80); // OLED_ONE_COMMAND_MODE
  Wire.write(0x10); // set higher column address
  Wire.endTransmission();
  //delayMicroseconds(2);
  // перевод в режим получения данных
  Wire.beginTransmission(_address);
  Wire.write(0x80); // OLED_ONE_COMMAND_MODE
  Wire.write(0x40); // OLED_DATA_MODE
  Wire.endTransmission();
  // delayMicroseconds(2);
  
  // 8 горизонтальных страниц памяти экрана
  for (uint8_t p = 0; p < 8; p++) {
    
    // выбор страницы
    Wire.beginTransmission(_address);
    Wire.write(0x80); // OLED_ONE_COMMAND_MODE
    Wire.write(0xB0 + p + 0); // set page address зачем + 0 ?
    Wire.endTransmission();
    // delayMicroseconds(2);
    // наверное, адрес относительно страницы, с которого начнем заполнять ОЗУ
    Wire.beginTransmission(_address);
    Wire.write(0x80); // OLED_ONE_COMMAND_MODE
    Wire.write(2 & 0xf); // set lower column address
    Wire.endTransmission();
    // delayMicroseconds(2);
    // наверное, адрес относительно страницы, до которого будем заполнять ОЗУ
    Wire.beginTransmission(_address);
    Wire.write(0x80); // OLED_ONE_COMMAND_MODE
    Wire.write(0x10); // set higher column address
    Wire.endTransmission();
    // delayMicroseconds(2);

    // отправляем 8 посылок по 16 байт из-за ограничений Wire.h
    
    // 8 блоков
    for (uint8_t b = 0; b < 8; b++) {
    
      // режим приема данных
      Wire.beginTransmission(_address);
      Wire.write(0x40); // OLED_DATA_MODE
      
      // 16 колонок в блоке
      for (uint8_t c = 0; c < 16; c++) Wire.write(B00001111); // тут нужно подставить соответствующий байт-столбец, координаты [b * 8 + c, p * 8]
      
      Wire.endTransmission();
      // delayMicroseconds(2);
      
    }
    
  }
  
}

Может просто забыл убрать при переписывании.

не забудь, что размер буфера Wire 32 байта. Превысишь - всё превратится в тыкву.

1 лайк

да, спс, как раз в мануалах пишут не более 16ти за раз - потом перезапуск сессии

если я, то не забыл… в библиотеке sendCommand(0x10); в 780 строке

Автор источника передирания забыл.
А может и сам передирал, да не понял зачем оно и оставил. Этож гайвер.

как будет на руках экранчик - проверю без этой команды…

тем не менее, спасибо челу за инфу на русском)) вести подобный каталог информации в доступном виде - сложно

Прикол экранчик предполагается в пульт по i2c, еще радиомодуль на Serial повешен. Плата pro micro. Пины прерывания: 0, 1, 2, 3, 7 (serial, serial, i2c, i2c, энкодер) Прям на тоненького получилось…

Тут есть варианты … даже без wire.h …
Эмулятор WOKWI | Аппаратная платформа Arduino

А тут i2c софтовый на любых пинах и отличной скорости… Программный i2c 1 МГц (для 16 МГц устройств) для AVR. | Аппаратная платформа Arduino
https://arduino.ru/forum/programmirovanie/programmnyi-i2c-1-mgts-dlya-16-mgts-ustroistv-dlya-avr?page=1

1 лайк

чисто на таймерах и регистрах если я правильно понял))) мощно, конечно

скопировал в копилочку ссылок, пока вроде хватает стандартных средств, но мало ли

Библиотеки тоже не сильно навредят. На RP2040 за 200 р. хоть с цветным, хоть с каким экраном, хоть сколько серв.

меня пугает перспектива использования библиотек, когда их нужно больше одной. Выясняться начинает, что они одни и те же выводы/таймеры/прерывания используют, или иные конфликты вызывают…

Вы всегда ездите ТОЛЬКО по дорогам с односторонним движением, т.к. на двухсторонней можно столкнуться со встречным?

PS. Вообще-то концепция Ардуино подразумевает использование не менее двух библиотек: одной для устройства ввода и другой - для устройства вывода.

Их в бой вводить можно постепенно. Сначала проверка ходовой на своих векторах с ограничением по времени движения. Потом пульт управления, потом метрии, потом кадроустановки и т.д. и т.п.