olikraus / u8g2

U8glib library for monochrome displays, version 2
Other
5.16k stars 1.05k forks source link

SSD1306 I2C Optimization (was: Commands with arguments not being sent correctly?) #735

Closed dveazie closed 5 years ago

dveazie commented 6 years ago

Trying to use u8x8 with an SSD1306 and I don't think the data is being sent down the I2C bus correctly. During initialization, the first command is "Set Display OFF" which is a command that takes no arguments. I see: START 01111000 0 (Setup Write to [0x3C] + ACK) 00000000 0 (0x00 + ACK) 10101110 0 (0xAE + ACK) STOP

All good so far. The next command is "Set Display Clock Divide Ratio/Oscillator Frequency" which takes an argument. I see: START 01111000 0 (Setup Write to [0x3C] + ACK) 00000000 0 (0x00 + ACK) 11010101 0 (0xD5 + ACK) STOP START 01111000 0 (Setup Write to [0x3C] + ACK) 00000000 0 (0x00 + ACK) 10000000 0 (0x80 + ACK) STOP

Is this correct? Or should it be all in 1 transaction like this: START 01111000 0 (Setup Write to [0x3C] + ACK) 00000000 0 (0x00 + ACK) 11010101 0 (0xD5 + ACK) 10000000 0 (0x80 + ACK) STOP

This happens for all of the commands that take arguments. In the past I used a display that had an ST7036 controller and I thought that all the commands that took arguments were done in 1 transaction. I don't have that project anymore so maybe I'm remembering wrong.

olikraus commented 6 years ago

Yes. It looks odd, but this is how it is described in the datasheet for I2C. It might be possible, that cmd and data can be switched within one transaction, but I never tested this. The other problem is this: Assume I have a long sequence of commands and args. I anyways have to split them into smaller pieces due to the restrictions of the Arduino Wire library. And finally: These commands are transfered only during startup. So there is (almost) no performance impact.

dveazie commented 6 years ago

Thank you for the quick reply olikraus. One thing I didn't make clear is that I'm not suggesting that the entire init_seq be sent in 1 transaction. But rather that maybe each command should be sent in 1 transaction.

The init_seq I'm using is u8x8_d_ssd1306_96x16_er_init_seq and the first few commands are:

  U8X8_C(0x0ae),                        /* display off */
  U8X8_CA(0x0d5, 0x080),        /* clock divide ratio (0x00=1) and oscillator frequency (0x8) */
  U8X8_CA(0x0a8, 0x00f),        /* multiplex ratio, 0.69 OLED: 0x0f */
  U8X8_CA(0x0d3, 0x000),        /* display offset, 0.69 OLED  */
  U8X8_C(0x040),                        /* set display start line to 0, 0.69 OLED */

As things are now, each byte is sent in its own transaction: U8X8_C(0x0ae) -> [START][0x78][0x00][0xAE][STOP] U8X8_CA(0x0d5, 0x080) -> [START][0x78][0x00][0xD5][STOP][START][0x78][0x00][0x80][STOP] U8X8_CA(0x0a8, 0x00f) -> [START][0x78][0x00][0xA8][STOP][START][0x78][0x00][0x0F][STOP] U8X8_CA(0x0d3, 0x000) -> [START][0x78][0x00][0xD3][STOP][START][0x78][0x00][0x00][STOP] U8X8_C(0x040) -> [START][0x78][0x00][0x40][STOP]

I'm wondering if instead it should be something like: U8X8_C(0x0ae) -> [START][0x78][0x00][0xAE][STOP] U8X8_CA(0x0d5, 0x080) -> [START][0x78][0x00][0xD5][0x80][STOP] U8X8_CA(0x0a8, 0x00f) -> [START][0x78][0x00][0xA8][0x0F][STOP] U8X8_CA(0x0d3, 0x000) -> [START][0x78][0x00][0xD3][0x00][STOP] U8X8_C(0x040) -> [START][0x78][0x00][0x40][STOP]

I'd also like to make it clear that I don't even know yet if what I'm proposing is correct or not. I've got a question into a rep for Solomon Systech and I haven't heard back yet. I also plan on connecting an Aardvark and trying some things out. I'll let you know what I find. Have a great day!

olikraus commented 6 years ago

Well, yes, I figured out one way how it works... So I kept it how it is...

olikraus commented 5 years ago

I did some tests here

  Uno U8G2_SSD1306_128X64_NONAME_F_HW_I2C   HW I2C      FPS: Clip=17.3 Box=19.5  @=6.6 Pix=8.8      each byte has its own start stop
  Uno U8G2_SSD1306_128X64_NONAME_F_HW_I2C   HW I2C      FPS: Clip=18.0 Box=20.3  @=6.6 Pix=9.0      start/stop for each command
  Uno U8G2_SSD1306_128X64_NONAME_F_HW_I2C   HW I2C      FPS: Clip=18.3 Box=20.8  @=6.7 Pix=9.1      one start/stop for multiple commands

The second measure is the implementation of your last post. It increased performance by about 4%. The third variant only sends one start at the beginning and then sends as much commands as possible.

Actually the whole thing is a little bit risky, because the number of bytes between start and stop is limited in the Arduino environment to 32 bytes, so the third version will only work if the overall number of bytes of the init sequence does not exceed 32 bytes (difficult for some more complex controllers)

The second version seems to be better, but still the number of args must not exceed 31 bytes. The problem might be the grayscale command for some OLEDs...

There will be now two CAD procedures:

    u8x8_cad_ssd13xx_i2c            classic version
    u8x8_cad_ssd13xx_fast_i2c       your suggestion from above (4% improvement)

I will apply this to SSD1306 controller for now. This is a list of other controllers, for which the fast procedure needs to be verified first:

SH1106 SH1107 SH1108 SH1122 SSD1309 SSD1317 SSD1325 SSD0323 SSD1326 SSD1327 ST7567 ST7588

olikraus commented 5 years ago

closing this...