Ah yes I was already looking into systemd mechanics for another project, thanks for the suggestion and explanation.
Also I will take a shot at compiling u8g2-arm-linux on the Bela, thanks.
Ah yes I was already looking into systemd mechanics for another project, thanks for the suggestion and explanation.
Also I will take a shot at compiling u8g2-arm-linux on the Bela, thanks.
giuliomoro This will increase the clock speed of the i2c bus from 100kHz to 400kHz. I don' t think this will by itself fix your performance issues, but it should help a bit.
Hi @giuliomoro - Made the updates as you detailed. It definitely sped it up! But you're right not quite enough to work for my application.
Any other thoughts on improvements? Is the library itself inefficiently displaying each pixel? What improvements could be made to more efficiently write to the screen?
I can get changes to reflect within 5 seconds after initially starting up the program, but after 1-2 minutes of it running the latency balloons to 30 seconds and then 1 minute, and so on.
Sorry, been a busy week.
This (reformatted from your code for improved readability) looks inefficient:
clearDisplay();
....
if (msg.match("/param1").popFloat(floatArg).isOkNoMoreArgs())
{
i++;
j=i;
arrayWidth[i] = floatArg*100;
for(j;j--;j<0)
drawPixel(j, arrayWidth[j], WHITE);
Display();
if(i>127)
i = 0;
}
First off, the line for(j;j--;j<0)
looks extremely confusing. the j
and j<0
expressions are actually unused, It is the equivalent to while(j--)
, but the latter is actually readable.
As far as I understand, in this loop you are drawing all the pixels up to the current index, which means that when i=0
you will only have one pixel displayed. As i
increases (as more messages come in), you will progressively fill up the screen, until i
reaches 127 (end of the display), at which point you will see the waveform covering the whole display horizontally, and then it restarts. Not sure why you'd want this behaviour.
As for performance, for each message that comes in you are calling clearDisplay()
(once), drawPixel()
(several times), Display()
(once). I had a quick look at the content of the SSD13306_OLED.c
file. It seems that the library keeps track internally of the value of each pixel on the display, and calls to clearDisplay()
or drawPixel()
only change this internal representation, therefore they are relatively cheap to call. Display()
, however, transmits the whole buffer to the display, and this is the real bottleneck: regardless of how many pixels you changed since the last time you called Display()
, calling it will always take the same time. Your messages are coming in about 43 times a second*, and you are therefore asking the display to redraw 43 times a second. This is a very high frame rate for this sort of display. The transfer()
function in the SSD13306 library is the one that is actually performing the data transmission, and it looks like it is transmitting 1024bytes=8192bits **. With a clock of 400kHz, this takes at least 8192/400000 = 20ms, giving - if I got the numbers right - a theoretical frame rate of 50Hz, however this number is likely to be much smaller because of overhead involved in I2c transfers.
One solution here would be to reduce the frame rate by not calling Display()
for every new pixel. The easiest approach (which will also save some CPU by reducing the number of OSC messages to process), would be not to send this many messages each containing one pixel, but rather as a pack of at least 4 pixels per message, though ideally I would send the whole display at one time. This way you would only call Display()
when you have a handful of pixels or even a full display to draw. When sending such large buffers you may want to send OSC messages as blob
instead of float
. Either way, drawing the display less often is going to be a big advantage.
Another way to increase the speed is trying to boost the clock of the I2c bus even further. It is unclear to me what the specified max frequency supported by the display is, and it may well vary between different specimens, as pointed out by a comment in the Adafruit library for Arduino, but it seems that it could possibly reach 1MHz or beyond. So you can try and amend the .dts
file as you have already done and change the frequency to 1000000
(follow procedure above). This is 1MHz, which is the maximum speed supported by the Bela side. Try to run the display like that and see if it works reliably. This would give about a 2.5x speed increase, allowing much faster transfer rates. Still, I'd encourage you to try out the approach above to throttle the number of times you are calling Display()
, as that would also reduce the CPU load.
Note: there may be ways of updating exactly one pixel in the display, without redrawing the whole screen, which would be significantly faster, but it doesn't look like neither the DeeplyEmbedded nor the Adafruit libraries support them.
* the[env~]
object will send out a new message every 1024 samples; assuming that Pd running on the host has a sampling rate of 44100Hz, that is 43 messages per second.
** there are actually more than these many bits, because of the START, STOP and ACK bits. The numbers I mention are therefore optimistic, also because there are actually 1024/16=64 calls to i2c_multiple_writes()
(and consequently to write()
), each of which comes with its own overhead.
@kreiff I amended the post above to fix some numbers in the refresh rate computations. I will try to measure these on the scope and see what it takes in practice.
@giuliomoro - First off, thanks, as always, for the extremely detailed reply and code review.
giuliomoro Display(), however, transmits the whole buffer to the display, and this is the real bottleneck: regardless of how many pixels you changed since the last time you called Display(), calling it will always take the same time.
Ahh! Ok - I didn't realize this redrew the whole screen for each call. Presumably I can just grab a snapshot of the waveform from PD every 20ms and redraw the whole updated waveform rather they redrawing the whole screen pixel by pixel.
giuliomoro ideally I would send the whole display at one time. This way you would only call Display() when you have a handful of pixels or even a full display to draw. When sending such large buffers you may want to send OSC messages as blob instead of float. Either way, drawing the display less often is going to be a big advantage.
Yeah - this definitely seems like the best way to approach it. I'm actually kind of relieved to know that the library is fairly efficient and it was just my approach and misunderstanding of the Display()
function that has been the issue.
giuliomoro it seems that it could possibly reach 1MHz or beyond.
I'll try to work within the confines of the 400kHz clock frequency for now. I clearly have to re-evaluate my approach to drawing the waveforms. I think if I can achieve 10-15 fps at 400kHz it would still be acceptable for this application.
I'll report back with my progress! Thanks again!
This is a super interesting thread and full of all sorts of in-depth details.
@kreiff Did you manage to ever sort out your display issues?
@giuliomoro Is there any movement on an official Bela library for i2c OLEDs or was that a challenge for the community?
Flowdeeps this is a stand-alone program that receives OSC and sends i2c to the display (generates no audio, but can run in parallel with a real Bela prorgam) http://github.com/giuliomoro/OSC2OLED4Bela
@Flowdeeps - I ended up taking a different route. My goal with the oled screen was to try any get an oscilloscope that I could use to monitor modulation in real time. I attempted to implement the Display() function the way that Giulio had recommended by grabbing a screen's worth of pixel values in an array and populating them every 20ms, but I wasn't able to get the array values to populate correctly - I'm not a particularly good C / C++ coder...
In the end, I resorted to setting up a wifi connection between my phone and the effect pedal I was building with a little wifi dongle and using the IDE oscilloscope via DAC 27. This worked really well for my application and honestly feels a little like the OP-Z use of a phone as a "screen".
I might try my hand at it again at a later date, but this isn't on my short list of projects to come back to.
Ive recently got a couple of OLEDS working on a terminal tedium....
code could be easily adapted for the Bela
https://github.com/TheTechnobear/TTuiLite
based on
https://github.com/TheTechnobear/ArduiPi_OLED.git
now Ive got it working on the TT, Im going to order a couple more SSH1306 and do something similar for the Bela Pepper ... lots of I2C working coming up, given Trill is imminent too
Hey guys,
Is it possible to use two devices at the same time (trill craft + oled screen) on the pins SDA, SCL, V+ AND GND of the bela black?
RafaeleAndrade
they are all I2C, so im hoping as long as they have different addresses this would be possible.
(this is whats happening with the 2 oleds on the TT above)
once the trill arrive, Im planning on making something with a few trill, a couple of i2c encoders and an oled !
the guy who supplied me with the above Terminal Tedium, pointed me to these :
https://www.tindie.com/products/saimon/i2cencoder-v21-connect-rotary-encoder-on-i2c-bus/
they are not cheap, but the idea is Im going to add one or two to the above terminal tedium 'expander',
and also use a few with the trill/bela
thetechnobear they are all I2C, so im hoping as long as they have different addresses this would be possible.
Yes. Each type of sensor has a dedicated range of address, which are non-overlapping between sensor types. You can set the sensor to a different address by applying a tiny amount of solder on the board to bridge two pads together.
thetechnobear awesome! This a great idea!
giuliomoro Thanks! Sometimes simple questions are so essencial for next steps
giuliomoro this is a stand-alone program that receives OSC and sends i2c to the display (generates no audio, but can run in parallel with a real Bela prorgam) http://github.com/giuliomoro/OSC2OLED4Bela
How would one go about running this in parallel if they wanted to run it on start up?
See the instructions above in this thread here. I am pretty sure @crosswick got it to work as a systemd
service, am I right?
giuliomoro Thank you. I've got this mostly to work. However whenever the OSC2OLED program runs for the first time when the Bela is started I am getting this error:
If it is run again the program runs fine and there is no error message. However when running it from start up it stops the other Bela program that is enabled to run on start up.
Are both your Bela program and the OSC2OLED program trying to access the same I2C device?
giuliomoro No just the OSC2LED. The Bela program is just a simple pd sketch that is sending OSC data to the screen for testing purposes at the moment.