• Hardware
  • is anyone successfully using i2c OLED / LCD?

Let's try to live with the low refresh rate but avoid messages piling up which cause the noticeable delay after a while. I will send you a patch later today hopefully.

thinking of migrating this Oled to the Principium keyboard - should be plenty fast to just display a patch name and a large number. don't really need to see live control changes..

still interested in the workings of such a patch, though, but it just seems to be a bit of an ill fit - seeing how the unused functionality of something supposedly more powerful actually causes a slowdown. 🙂

tried it with a 128x64 SSD1306 Oled i had lying around.
can confirm this one does work beautifully.

conclusion: SSD1327, not an ideal combo w/ U8g2.

    well, after some fiddling with the setup function
    (my display needs {U8G2LinuxI2C(U8G2_R0, gI2cBus, 0x3c, u8g2_Setup_ssd1327_i2c_midas_128x128_f), -1} to work, apparently)

    • it still works, as in: it's responsive to osc messages.
    • i guess it avoids the pileup, as in: response time when switching between functions is much better.

    however, the LFO's and array waveform only get displayed partially, like top quarter of the screen. rest of the screen remains black. when you stop the LFO's or waveform, the whole display is filled with what it should display. same goes for parameters: a bang there causes only parameter 1 to be displayed, with varying length of its block underneath, and at what i assume is the end of the last [line], all three get displayed. same behaviour as the lfo's, really.

    feels like it doesn't get the time to fully write the contents of the buffer to the screen before refreshing.
    i totally appreciate the effort, but i'm not sure if it's worth it. not for my use case, anyway..

    Try removing the if(!sent) line from the bottom

    hmm. same.
    alt text

    this is the LFO example.

    And increase the usleep in the next line to 100000 ?

    yes! definitely the best one yet.
    don't know what you did exactly, but it seems to work. 🙂

    i mean, you can still see it struggling.. parameter blocks are jumpy and especially on the sine waveform you can see the screen rolling. However! timing is as accurate as it will be and the buffer buildup is gone. yay!

    i wouldn't use it for waveform displays as they are nowhere near smooth enough, but for most other functions this is beautiful. hats off to you sir.
    mind explaining your process? am i correct in thinking this is at 1/4 refresh rate now?

      Remork mind explaining your process? am i correct in thinking this is at 1/4 refresh rate now?

      see the diff here: https://github.com/giuliomoro/O2O/compare/only-latest

      Sending data to the screen has been moved out of the receiving thread (parseMessage()) and moved to the main thread (main()). parseMessage() parses all incoming messages and updates the in-memory buffer with the bitmap representation that has to be sent out to the display. When this buffer has been modified, it notifies the main thread (by setting the global gShouldSend per each display whose buffer has been updated), which proceeds to send the buffer to the display(s), ultimately causing the output to be visualised. Access to the in-memory buffers and the gShouldSend flags are protected by the mutex mtx, so that at any given time only one of parseMessage() or main() can read from or write to the buffer. The rate limiting is done by the fact that it doesn't matter how many times parseMessage() has updated the buffer while main() was sleeping: only the latest version of it will be written to the output. As parseMessage() will in principle run much faster than main()'s sleep, main() will be dropping frames, thus avoiding the backlog.

      The max frame rate is sort of limited by the time it takes to send data to the screen plus any sleep in main(). There is usleep(50000) in main(), but actually this sleep is only observed if no data has just been sent out, so that this should only affect the jitter of isolated messages (delaying their visualisation by up to 50ms), so if you are sending a steady stream of messages, there will be no sleep and it will immediately try to lock the mtx again.

      Now that I think of it again, we are sort of over-relying on the Linux scheduler to ensure we have enough CPU time to process incoming messages and you could find yourselves having both threads either busy or waiting on a lock and so little progress may be made on the parseMessage() side, still potentially leading to a backlog. This is because the "critical section" (i.e.: the section where the mutex is held) in main() is rather long (i.e.: it takes a lot of time because it's sending a lot of bits over I2C, whereas best practice suggests to keep critical sections as short as possible and without I/O. It is possible that a better approach would be to make a deep copy of the U8G2 object that we want to send out to, but this requires changing things deep inside it.
      I understand you are not seeing such issue right now, but I am thinking it could happen if the CPU gets busy with something else (e.g.: running Bela audio). One option here would be to change the priority of parseMessage() thread so that it is higher than main() (they are both at priority 0 right now. Alternatively, more simply, you could just force a sleep even in case something has just been sent.
      Back of the envelope (optimistic) frame rate calculation:

      payload (bits): 128*128*4 = 65536
      transfer rate: 400000 bits/s
      min transfer time (optimistic, assumes bus fully occupied and no transaction overheads): 65536/400000 = 0.16s
      max frame rate = 1/0.16s = 6.25Hz

      So even adding 1ms of sleep after just sending some data will not significantly slow down the frame rate, but may give parseMessage() more CPU time to process incoming messages. Something like this would be nice:

      		if(sent)
      			usleep(1000); //short sleep
      		else
      			usleep(50000); //long sleep

      Further note:

      I scoped the I2C bus on Bela and it looks like instead of running at the nominal 400kHz it is doing 333kHz. This means we are actually below that, but I see no straightforward way of increasing that (the datasheet lists 100kHz or 400kHz as the only values) and it's probably not worth the effort here as the display's sampling rate wouldn't actually improve much here.
      Table 13-6 of the SSD1327 datasheet shows that min "Clock Cycle Time" is 2.5us, i.e.: 400kHz seems to be the highest clock you could use here and we are already close to that.

      alt text

        giuliomoro I scoped the I2C bus on Bela and it looks like instead of running at the nominal 400kHz it is doing 333kHz.

        correction: with pull-up resistors this actually goes higher. I am at 384kHz with 10k pullups. Probably reaches the nominal 400kHz with more reasonable 2k2 pullups.

        giuliomoro Sending data to the screen has been moved out of the receiving thread (parseMessage()) and moved to the main thread (main()). parseMessage() parses all incoming messages and updates the in-memory buffer with the bitmap representation that has to be sent out to the display. When this buffer has been modified, it notifies the main thread (by setting the global gShouldSend per each display whose buffer has been updated), which proceeds to send the buffer to the display(s), ultimately causing the output to be visualised. Access to the in-memory buffers and the gShouldSend flags are protected by the mutex mtx, so that at any given time only one of parseMessage() or main() can read from or write to the buffer. The rate limiting is done by the fact that it doesn't matter how many times parseMessage() has updated the buffer while main() was sleeping: only the latest version of it will be written to the output. As parseMessage() will in principle run much faster than main()'s sleep, main() will be dropping frames, thus avoiding the backlog.
        The max frame rate is sort of limited by the time it takes to send data to the screen plus any sleep in main(). There is usleep(50000) in main(), but actually this sleep is only observed if no data has just been sent out, so that this should only affect the jitter of isolated messages (delaying their visualisation by up to 50ms), so if you are sending a steady stream of messages, there will be no sleep and it will immediately try to lock the mtx again.

        okay. i understand that part 🙂 couldn't have come up w/ that myself, as i don't grasp the concepts used enough to know that such a move would result in more stability. it makes sense when you explain it, though.

        the part about the linux scheduler i'll have to read twice. 🙂
        but i understand that if we're at 333kHz, without pullups, we're slower than the max rate of 6.25Hz?
        which seems sort of slow to begin with - how does that compare to 'standard' monochrome Oled?
        i mean, i can clearly see the difference, maybe that's all that matters.

        thinking of which, would these adjustments also improve speed on a regular SSD1306, for example?

          Remork thinking of which, would these adjustments also improve speed on a regular SSD1306, for example?

          I think something similar to these adjustments is needed only when you are sending data faster than can be transferred to the screen, so that the pile up starts. For any display going through a 400kHz I2C bus you can do similar calculations, but the payload will be different. Above

          payload (bits): 128*128*4 = 65536

          is 128x128 pixels at 4 bit each. An SSD1306 is monochrome, 128x64, so the per-frame payload is 1/8 of that of the SSD1327 (128*64*1 = 8192) and so the theoretical frame rate is 8x at 48Hz. Now, it's unlikely you'll reach that when the CPU is doing other things as well, but you probably would be happy with half that anyhow and that is totally achievable. For grayscale or color screens you should probably consider using SPI instead, as that can easily reach over 10Mbps.

          Remork but i understand that if we're at 333kHz, without pullups, we're slower than the max rate of 6.25Hz?

          Yes, possibly. That is what I measured on Bela without any load on the bus, but it may be that your board already has pullups on that bus, or that the screen breakout has them, or that the simple presence of the screen on the bus makes it slightly faster (I2C is a bit of a weird protocol in terms of "speed"...). Or you can add some 2k2 resistors just in case. Point is, you are not going to get much better than that.

          Remork thinking of which, would these adjustments also improve speed on a regular SSD1306, for example?

          They shouldn't hurt. Ultimately these only kick in if you are sending data to Bela faster than it can be sent to the screen. While this is a useful catch-all workaround, I would recommend instead that you limit the rate at which you send messages to O2O, so that you save the CPU time spent parsing messages and rendering frames that will eventually not be displayed.

          9 months later

          Remork I came across this thread because I am also implementing an SDD1306 OLED. I can get everything working, but I have extreme delays too. I am sending via Max/MSP OSC messages with /params and only get good results by adding a 50 ms speedlim. Did you solved the delays problem? A help would be greatly appreciated

          If the processor on Bela was only sending I2C data to the OLED, it would take 128*64/8*10/400000 = 25.6ms to send a frame (10 being the number of clock slots required to transmit 8 bits via I2c), so that's the hard limit for refresh rate. Considering the communication overhead of the I2C bus and that the processor is also doing other things (at the very least processing audio and OSC), I think 50 ms is a good result and I am not sure it can be pushed much faster. If you want to go faster than that, you should be looking at SPI displays that allow much higher transfer rates.

          thank you @giuliomoro, super clear. I will adapt to this speed for the moment.

          But I have a 7-Segment Serial Display which can communicate via SPI. Are there any examples for a 7-Segment Serial Display implementation via SPI? I just found this thread https://forum.bela.io/d/754-7-segment-display.

          Or would you have a recommended SPI display tested with Bela? Thank you very much!

            albert But I have a 7-Segment Serial Display which can communicate via SPI.

            Do you have a datasheet for it ? This could be probably driven from two Bela GPIOs using soft-SPI, given the very small bandwidth it will probably require ...

            albert Or would you have a recommended SPI display tested with Bela? Thank you very much!

            there were some posted here in the past, including an RGB one ... I should note that on Bela there is no spare SPI bus, so you'd have to sacrifice either audio inputs or analog inputs. On Bela Mini, on the other hand, there is a spare SPI bus available and ready.