Чтение оперативной памяти с OLED-контроллера SH1106 через I2C

Вывод: у меня возникли проблемы с написанием программы для получения данных ОЗУ от OLED-контроллера SH1106 через i2c. В первой части вопроса я написал предположения, которые я сделал, чтобы получить информацию, которую я не смог найти в таблице данных, во второй части я разместил программу, которая должна читать эти данные, но не (очевидно). , так как я пишу здесь...).

Я работаю над программой для управления OLED-дисплеем SH1106. Я прочитал техническое описание, чтобы узнать, как отправлять на него команды, и оно работает, я могу писать на свой дисплей.
Теперь в даташите есть один момент, которого я не понимаю: как я могу прочитать оперативную память?

Рассмотрите рисунок ниже, взятый из командной таблицы на страницах 30 и 31 описания SH1106; последняя строка посвящена чтению оперативной памяти.
Этот чип можно подключить к микроконтроллеру через разные интерфейсы: 6800 и 8800 8-битный параллельный, 3-х и 4-х проводной SPI и i2c. Поскольку я использую SPI, у меня нет контактов A0, RD или WR, и я не нашел их нигде в таблице данных. «перевод» логического значения, присвоенного этим сигналам в таблице команд, в некоторый сигнал i2c. Я предполагаю, что WR cor отвечает младшим битом адреса i2c, поскольку он равен 0 для записи и 1 для чтения. И мое предположение кажется верным, поскольку я могу прочитать статус (строка 25).
Затем я предположил, что A0 должен соответствовать DC (данные/команда, см. второй снимок экрана), бит контрольного байта. Байт управления — это байт, который должен быть отправлен перед каждым байтом или группой байтов, чтобы сообщить чипу, как его интерпретировать (команду или данные). Но я пытался отправить его перед чтением i2c с другими значениями, и я всегда получаю байт состояния (никогда не данные ОЗУ).

Как я мог прочитать память микросхемы SH1106 через i2c? В даташите не указано, что это невозможно (и то, что это возможно...), может быть, я просто не могу?

Таблица команд Таблица команд (Я удалил строки 1-16, которые содержат команды, подобные тем, что находятся в строках 17-23)


Байт управления Управляющий байт


Вот самая короткая работающая программа, которую я смог написать, которая показывает проблему (т.е. содержит ошибки моей реализации). Я протестировал его на Arduino UNO.

#define ADDR  0x78


void i2c_start() {
  TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN);
  while (!(TWCR & (1 << TWINT)));
}

void i2c_stop() {
  TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
}

void i2c_send(uint8_t data) {
  TWDR = data;
  TWCR = (1 << TWINT) | (1 << TWEN);
  while (!(TWCR & (1 << TWINT)));

}

uint8_t i2c_read(bool isLastByte) {

  if (isLastByte) TWCR = (1 << TWINT) | (1 << TWEN);
  else TWCR = (1 << TWEA) | (1 << TWINT) | (1 << TWEN);
  while (!(TWCR & (1 << TWINT)));
  return TWDR;
}


void display_init() {
  i2c_start();
  i2c_send(ADDR);
  i2c_send(0x00); //................ байт управления
  i2c_send(0xAE); //................ displayOff
  i2c_send(0x8D); //................ *
  i2c_send(0xD5); i2c_send(0x80); // тактовая частота
  i2c_send(0x32); //................ насосНапряжение0123
  i2c_send(0xA1); //................сегментПереназначить
  i2c_send(0xC8); //................флипВертикально
  i2c_send(0xDA); i2c_send(0x12); // comConfiguration
  i2c_send(0xA8); i2c_send(0x3F); // мультиплекс
  i2c_send(0xD3); i2c_send(0x3F); // отображениеСмещение
  i2c_send(0x10); i2c_send(0x00); // адрес столбца
  i2c_send(0x40); //................ стартовая строка
  i2c_send(0xB0); //................адрес_страницы
  i2c_send(0xAF); //................ displayOn
  i2c_stop();
}


void setup() {

  // Инициализация
  Serial.begin(115200);
  display_init();

  // Запрос данных ОЗУ
  i2c_start();
  i2c_send(ADDR);
  i2c_send(0x80); // управляющий байт
  i2c_send(0xB0); // адрес страницы
  i2c_send(0x80); // управляющий байт
  i2c_send(0x10); // старший адрес столбца
  i2c_send(0x80); // управляющий байт
  i2c_send(0x00); // низкий адрес столбца
  i2c_send(0x40); // управляющий байт
  i2c_send(0xE3); // нет
  i2c_stop();

  // получить данные оперативной памяти
  uint8_t a[10];
  i2c_start();
  i2c_send(ADDR + 1);
  for (int i = 0; i < 9; i++) a[i] = i2c_read(false);
  a[9] = i2c_read(false);
  i2c_stop();

  // печать результатов
  for (int i = 0; i < 10; i++) {
    Serial.print("0x");
    Serial.println(a[i], HEX);
  }

}

void loop() {}

В разделе // Request RAM data я перепробовал столько возможностей, сколько мог себе представить. Чтобы избежать путаницы, я оставляю только опубликованный для вашего анализа, но, конечно, если кто-то хочет их увидеть, просто напишите это в комментарии, и я опубликую все ( неудачные) тесты, которые я сделал.

Вывод на последовательный монитор всегда представляет собой список 0x3s.

Я не стал сокращать процедуру инициализации, которую использую в своем коде, поскольку, возможно, проблема связана с этим. Кстати, я понятия не имею, что такое i2c_send(0x8D); //................ строка * есть, я добавил ее, потому что она есть во всех других библиотеках SH1106, которые я нашел, но я не нашел ее в мой техпаспорт. Может быть, есть несколько версий чипа или даташита?

, 👍1


2 ответа


0

Несколько сложно разобраться в протоколе I2C. Немного, чтобы посмотреть:

Если бит R/W установлен в единицу в адресе подчиненного устройства, микросхема будет выводить данные сразу после адреса подчиненного устройства в соответствии с битом D/C, который был отправлен во время последнего доступа для записи. Если мастер не генерирует подтверждения после байта, драйвер прекращает передачу данных мастеру.

Поэтому вам нужно установить бит D/C в транзакции до того, как вы захотите получить доступ к ОЗУ.

Из таблицы неясно, нужно ли отправлять байт данных после управляющего байта, чтобы он был принят, но если вы это делаете, вы можете использовать команду NOP.

Итак, вы выполняете запись NOP с D/C (что, да, соответствует A0 в других интерфейсах), установленным в 1 в управляющем байте (т. е. записываете байты 0x40 0xE3), а затем вы можете выполнить чтение который должен возвращать содержимое ОЗУ по текущему адресу страницы/столбца, который будет автоматически увеличиваться для каждого последующего байта.

Это довольно нечеткое техническое описание, когда дело доходит до объяснения того, как работает чтение I2C...

,

Спасибо за ваш ответ. Я протестировал его (в частности, я не рассматривал возможность использования NOP после управляющего байта 0x40), но я все еще не могу прочитать данные. Я отредактировал свой вопрос, добавив кратчайший код, который я мог написать, который все еще содержит и показывает проблему., @noearchimede


1

В моей версии таблицы данных есть блок-схема (рис. 10) выполнения операций "чтение-изменение-запись". В этом есть подсказка - должно быть фиктивное чтение, прежде чем будет выполнено фактическое чтение. Вот моя функция для построения пикселя, которая требует чтения-изменения-записи для добавления пикселя к существующим данным. Неясно, нужно ли фиктивное чтение перед каждым прочитанным байтом или только с первым из последовательности байтов, но это достаточно легко проверить.

#define CTRL_LAST_CMD 0x00
#define CTRL_NEXT_CMD 0x80
#define CTRL_LAST_RAM 0x40
#define CTRL_NEXT_RAM 0xC0


void MySH1106_PlotPixel(char x, char y, char on) {
  unsigned char curDisp;
  MySH1106_SetPage(y/8);
  MySH1106_SetCol(x);

  Wire.beginTransmission(SLAVE_ADDR);
  Wire.write(CTRL_LAST_CMD);
  Wire.write(0xE0); //РМВ включить
  Wire.endTransmission(1);

  Wire.beginTransmission(SLAVE_ADDR);
  Wire.write(CTRL_LAST_RAM);
  Wire.endTransmission(1);

  Wire.requestFrom(SLAVE_ADDR, 2, 1);
  while(Wire.available()<2);
  curDisp = Wire.read(); //фиктивное чтение
  curDisp = Wire.read(); //настоящее чтение.

  Wire.beginTransmission(SLAVE_ADDR);
  Wire.write(CTRL_LAST_RAM);
  if (on) {
    Wire.write((0x1<<(y%8)) | curDisp);
  } else {
    Wire.write(~(0x1<<(y%8)) & curDisp);
  }
  Wire.beginTransmission(SLAVE_ADDR);
  Wire.write(CTRL_LAST_CMD);
  Wire.write(0xEE);//конец RMW
  Wire.endTransmission(1);

}

Это ужасно неэффективно для построения графика в один пиксель, но для начала положено.

Редактировать: впоследствии я подтвердил, что требуется только 1 байт фиктивного чтения, даже если чтение остановлено/перезапущено. Пока вы выполняете только чтение из ОЗУ, дальнейшие фиктивные чтения не нужны.

,