[ 0.702571] omap_i2c 44e0b000.i2c: bus 0 rev0.11 at 400 kHz
[ 0.704215] omap_i2c 4802a000.i2c: bus 1 rev0.11 at 400 kHz
[ 0.707380] omap_i2c 4819c000.i2c: bus 2 rev0.11 at 400 kHz
is anyone successfully using i2c OLED / LCD?
OK that looks good. Let's see about this performance issue, then: do you get this "delay" even when running O2O
alone, without any Bela program?
not sure if that's what you mean, but this is with just O2O
running on Bela/Pepper, and the pd example patch running on my laptop.
i don't have O2O
running in the background, with another project running on Pepper, if that's the question.
yes that's the question. I guess this is unexpected, then. Can you disable all metros from the Pd patch and just use the following (with the sliders set to a range of 0:1 ) and see if you still notice the latency when adjusting one of the sliders manually:
will try.
but i found this on an arduino forum:
'The SSD1327 indeed is a graylevel controller (4 bit per pixel). There is no monochrome mode and no graphics acceleration: So, u8g2 has to transfer 4 bit for one pixel.
Also remember, that u8g2 is a monochrome library. U8g2 can not use all the different shades of gray: 0 is mapped to 0000 and 1 to 1111.
Maybe the full page mode constructor will be a little bit faster (if you have something bigger than an Uno), but in general, there is nothing much what you could do.'
so sounds like SSD1327 is just a bad match for the U8g2 lib..
will look into other screens then - probably easier than reinventing the wheel on the lib front.
- Edited
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.
- Edited
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.
oh OK. would you mind trying this branch out with the SSD1327 and see whether:
- it still works (untested)
- it avoids the pileup of messages in the queue, only displaying the most recent one
- Edited
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.
this is the LFO example.
And increase the usleep in the next line to 100000 ?
@Remork I added another commit that should fix that . Try it pls
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.
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?
- Edited
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.