user23991: Summary: I'm having trouble with writing a program to get RAM data from an SH1106 OLED controller via i2c. In the first part of the question I've written the assumptions I've made to get informations I couldn't find in the datasheet, in the second part I've posted a program which should read that data but doesn't (obviously, since I'm writing here...).

I'm working on a program to control an SH1106 OLED display. I read the datasheet to find out how to send commands to it and it works, I can write to my display.
Now in the datasheet there is one thing I don't understand: how can I read the RAM?
Consider the picture below, taken from the command table at pages 30 and 31 of the SH1106 datasheet; the last line is the one about reading RAM.
This chip can be connected to a microcontroller through different interfaces: 6800 and 8800 8-bit parallel, 3 and 4 wire SPI and i2c. Since I'm using SPI I don't have any A0, RD or WR pin, and I didn't found anywhere in the datasheet a "translation" of the boolean value given to those signals in the command table to some i2c signal. I assume that WR corresponds with the lsb of the i2c address, since it is 0 for writing and 1 for reading. And my assumption seems to be true since I can read the status (line 25).
Then I assumed that A0 should match DC (Data/Command, see second screenshot), a bit of the Control byte. The control byte is a byte that has to be sent before each byte or group of bytes to tell the chip how to interpret it (command or data). But I tried to send it before an i2c reading with different values and I always get the status byte (never the RAM data).
How could I read the memory of an SH1106 chip through i2c? The datasheet doesn't states that it is impossible (neither that it is possible...), maybe I simply can't?
Command table

(I removed lines 1-16, which contain commands like those at lines 17-23)

Control byte


EDIT
Here is the shortest working program I could write which shows the problem (i.e. which contains my implementation errors). I tested it on an 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); //................ control byte
  i2c_send(0xAE); //................ displayOff
  i2c_send(0x8D); //................   *
  i2c_send(0xD5); i2c_send(0x80); // clockFrequency
  i2c_send(0x32); //................ pumpVoltage0123
  i2c_send(0xA1); //................ segmentRemap
  i2c_send(0xC8); //................ flipVertically
  i2c_send(0xDA); i2c_send(0x12); // comConfiguration
  i2c_send(0xA8); i2c_send(0x3F); // multiplex
  i2c_send(0xD3); i2c_send(0x3F); // displayOffest
  i2c_send(0x10); i2c_send(0x00); // columnAddr
  i2c_send(0x40); //................ startLine
  i2c_send(0xB0); //................ pageAddr
  i2c_send(0xAF); //................ displayOn
  i2c_stop();
}


void setup() {

  // Initialization
  Serial.begin(115200);
  display_init();

  // Request RAM data
  i2c_start();
  i2c_send(ADDR);
  i2c_send(0x80); //   control byte
  i2c_send(0xB0); // page address
  i2c_send(0x80); //   control byte
  i2c_send(0x10); // column address high
  i2c_send(0x80); //   control byte
  i2c_send(0x00); // column address low
  i2c_send(0x40); //   control byte
  i2c_send(0xE3); // nop
  i2c_stop();

  // get RAM data
  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();

  // print results
  for (int i = 0; i < 10; i++) {
    Serial.print("0x");
    Serial.println(a[i], HEX);
  }

}

void loop() {}

I've tried as many possibilities as I could imagine in the // Request RAM data section. To avoid confusion I leave only the posted one to your kind analysis, but of course if someone would like to see them just write it in a comment and I'll post all the (unsuccessful) test I have made.
The output on the serial monitor is always a list of 0x3s.
I've not reduced the initialisation routine I use in my code because maybe the issue comes from there. BTW, I have no idea about what the i2c_send(0x8D); //................   * line does, I've added it because all the others SH1106 libraries I found had it, but I didn't found it in my datasheet. Maybe there are multiple versions of the chip or of the datasheet?

user62921: My version of the data sheet has a flowchart (Figure 10) for doing a "read-modify-write".  In there is a clue - there has to be a dummy read before the actual read is done.  Here is my function for plotting a pixel, which requires a read-modify-write to add a pixel to the existing data.  It's not clear dummy read is needed before every byte read, or just on the first of a sequence of bytes, but that's easy enough to test.
#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); //R-M-W enable
  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(); //dummy read
  curDisp = Wire.read(); //real 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);//end R-M-W
  Wire.endTransmission(1);

}

This is woefully inefficient for plotting one pixel, but it's a start.
Edit: I've subsequently confirmed that only 1 byte of dummy read is needed, even if the reads are stopped/restarted.  As long as you're only doing RAM reads, no further dummy reads are needed.

23991: <p><sub><em>Summary: I'm having trouble with writing a program to get RAM data from an SH1106 OLED controller via i2c. In the first part of the question I've written the assumptions I've made to get informations I couldn't find in the datasheet, in the second part I've posted a program which should read that data but doesn't (obviously, since I'm writing here...).</em></sub>
<br><br></p>

<p>I'm working on a program to control an SH1106 OLED display. I read the datasheet to find out how to send commands to it and it works, I can write to my display.<br>
Now in the datasheet there is one thing I don't understand: how can I read the RAM?</p>

<p>Consider the picture below, taken from the command table at pages 30 and 31 of the <a href="http://www.allshore.com/pdf/SH1106.pdf" rel="nofollow noreferrer">SH1106 datasheet</a>; the last line is the one about reading RAM.<br>
This chip can be connected to a microcontroller through different interfaces: 6800 and 8800 8-bit parallel, 3 and 4 wire SPI and i2c. Since I'm using SPI I don't have any <code>A0</code>, <code>RD</code> or <code>WR</code> pin, and I didn't found anywhere in the datasheet a "translation" of the boolean value given to those signals in the command table to some i2c signal. I assume that WR corresponds with the lsb of the i2c address, since it is 0 for writing and 1 for reading. And my assumption seems to be true since I can read the status (line 25).<br>
Then I assumed that <code>A0</code> should match <code>DC</code> (Data/Command, see second screenshot), a bit of the Control byte. The control byte is a byte that has to be sent before each byte or group of bytes to tell the chip how to interpret it (command or data). But I tried to send it before an i2c reading with different values and I always get the status byte (never the RAM data).</p>

<p>How could I read the memory of an SH1106 chip through i2c? The datasheet doesn't states that it is impossible (neither that it is possible...), maybe I simply can't?</p>

<p><strong>Command table</strong>
<a href="https://i.stack.imgur.com/pxSuW.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/pxSuW.png" alt="Command table"></a>
(I removed lines 1-16, which contain commands like those at lines 17-23)
<br><br><br>
<strong>Control byte</strong>
<a href="https://i.stack.imgur.com/nA1Xx.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/nA1Xx.png" alt="Control byte"></a></p>

<hr>

<p><strong>EDIT</strong></p>

<p>Here is the shortest working program I could write which shows the problem (i.e. which contains my implementation errors). I tested it on an Arduino UNO.</p>

<pre><code>#define ADDR  0x78


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

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

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

}

uint8_t i2c_read(bool isLastByte) {

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


void display_init() {
  i2c_start();
  i2c_send(ADDR);
  i2c_send(0x00); //................ control byte
  i2c_send(0xAE); //................ displayOff
  i2c_send(0x8D); //................   *
  i2c_send(0xD5); i2c_send(0x80); // clockFrequency
  i2c_send(0x32); //................ pumpVoltage0123
  i2c_send(0xA1); //................ segmentRemap
  i2c_send(0xC8); //................ flipVertically
  i2c_send(0xDA); i2c_send(0x12); // comConfiguration
  i2c_send(0xA8); i2c_send(0x3F); // multiplex
  i2c_send(0xD3); i2c_send(0x3F); // displayOffest
  i2c_send(0x10); i2c_send(0x00); // columnAddr
  i2c_send(0x40); //................ startLine
  i2c_send(0xB0); //................ pageAddr
  i2c_send(0xAF); //................ displayOn
  i2c_stop();
}


void setup() {

  // Initialization
  Serial.begin(115200);
  display_init();

  // Request RAM data
  i2c_start();
  i2c_send(ADDR);
  i2c_send(0x80); //   control byte
  i2c_send(0xB0); // page address
  i2c_send(0x80); //   control byte
  i2c_send(0x10); // column address high
  i2c_send(0x80); //   control byte
  i2c_send(0x00); // column address low
  i2c_send(0x40); //   control byte
  i2c_send(0xE3); // nop
  i2c_stop();

  // get RAM data
  uint8_t a[10];
  i2c_start();
  i2c_send(ADDR + 1);
  for (int i = 0; i &lt; 9; i++) a[i] = i2c_read(false);
  a[9] = i2c_read(false);
  i2c_stop();

  // print results
  for (int i = 0; i &lt; 10; i++) {
    Serial.print("0x");
    Serial.println(a[i], HEX);
  }

}

void loop() {}
</code></pre>

<p>I've tried as many possibilities as I could imagine in the <code>// Request RAM data</code> section. To avoid confusion I leave only the posted one to your kind analysis, but of course if someone would like to see them just write it in a comment and I'll post all the (unsuccessful) test I have made.</p>

<p>The output on the serial monitor is always a list of <code>0x3</code>s.</p>

<p>I've not reduced the initialisation routine I use in my code because maybe the issue comes from there. BTW, I have no idea about what the <code>i2c_send(0x8D); //................   *</code> line does, I've added it because all the others SH1106 libraries I found had it, but I didn't found it in my datasheet. Maybe there are multiple versions of the chip or of the datasheet?</p>
 62921: <p>My version of the data sheet has a flowchart (Figure 10) for doing a "read-modify-write".  In there is a clue - there has to be a dummy read before the actual read is done.  Here is my function for plotting a pixel, which requires a read-modify-write to add a pixel to the existing data.  It's not clear dummy read is needed before every byte read, or just on the first of a sequence of bytes, but that's easy enough to test.</p>

<pre><code>#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); //R-M-W enable
  Wire.endTransmission(1);

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

  Wire.requestFrom(SLAVE_ADDR, 2, 1);
  while(Wire.available()&lt;2);
  curDisp = Wire.read(); //dummy read
  curDisp = Wire.read(); //real read.  

  Wire.beginTransmission(SLAVE_ADDR);
  Wire.write(CTRL_LAST_RAM);
  if (on) {
    Wire.write((0x1&lt;&lt;(y%8)) | curDisp);
  } else {
    Wire.write(~(0x1&lt;&lt;(y%8)) &amp; curDisp);
  }
  Wire.beginTransmission(SLAVE_ADDR);
  Wire.write(CTRL_LAST_CMD);
  Wire.write(0xEE);//end R-M-W
  Wire.endTransmission(1);

}
</code></pre>

<p>This is woefully inefficient for plotting one pixel, but it's a start.</p>

<p>Edit: I've subsequently confirmed that only 1 byte of dummy read is needed, even if the reads are stopped/restarted.  As long as you're only doing RAM reads, no further dummy reads are needed.</p>
