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.