Now that I’ve calculated the theoretical framerate limits, it’s time to measure the actual framerates my Adalight device is putting out.
Using a logic analyzer and an Arduino Nano, I’m going to measure the framerate at varying Prismatik “Grab Intervals” and baud rates, and compare those numbers to what my calculations predict will happen.
If you have not read through the increasing framerate and theoretical framerate posts, I would suggest giving them a once-over before continuing. I’m going to jump right into the results, but you can find all of the information on the hardware, software, and methodology for this test at the end of this post.
From my knowledge of both Adalight and Prismatik, here’s are my predictions for what I think will happen:
- The framerate will be the same between all baud rates until the grab rate exceeds the frame time for a given baud rate.
- As the grab interval decreases, framerate will increase until it reaches a peak, at which point it will drop dramatically and stay low.
- This framerate peak will occur around the point that grab interval equals the combined frame time.
In regards to Prismatik, I’m using two different terms to talk about the same process: grab interval and grab rate.
Grab Interval – The gap of time between when the software “grabs” a new frame for the LEDs from the monitor. In Prismatik you set this in milliseconds (ms).
Grab Rate – How frequently these “grabs” occur per second. In an ideal world this is equal to the framerate.
The concept of frame time is explained in far more detail in the calculations post, but in a nutshell it is the theoretical amount of time required to send and display a single frame using Adalight, given a baud rate, number of LEDs, and the LED type.
With no further ado, here is a graph of the results:
That’s close, but yet not quite what I expected. Let’s look at each of my predictions in-turn.
Prediction #1: Equal Framerates
The framerate will be the same between all baud rates until the grab rate exceeds the frame time for a given baud rate.
This was true. Ignoring 57600 baud (which saturates at ~45 ms), you can see that for grab rates 50 through 20 the framerates for 115200, 250000, and 500000 baud are more-or-less equal.
This makes sense. Regardless of how much bandwidth is available on the data line, we’re only telling Prismatik to capture and send a certain number of frames. All good so far.
PREDICTION #2: Framerate Peak
As the grab interval decreases, framerate will increase until it reaches a peak, at which point it will drop dramatically and stay low.
This one is only partially true… the theory is that as the grab interval decreases, more data is pushed through the serial line. This will keep causing the framerate to increase until there are no more gaps between frame data on the serial line. Without these gaps during which the Arduino disables interrupts to transfer the data to the WS2812B LEDs, serial information will be sent and lost resulting in dropped frames (more information on that topic here). These dropped frames still take up time on the serial line, but don’t result in color data sent to the LEDs. This lowers the overall framerate, which shouldn’t recover as the data rate is increased further.
This occurred as-predicted for both 57600 and 115200 baud. At grab rates of 33-32 ms and 20-19 ms, respectively, the framerate reached its peak, and then fell dramatically and stayed there.
For 250000 and 500000 baud something strange happened. The framerate increased until it peaked, dropped dramatically, and then recovered. For 500000 baud it actually reached a higher peak. I’m not entirely sure why this happens.
I did test with my actual ambilight at 500000 baud, and for grab rates below 9 there is a serious problem with flickering. Increasing the grab rate value fixes the flickering immediately without resetting. There could be a number of reasons for this, anything from a power issue to serial noise to a software bug (either in my own Adalight code or Prismatik).
Update 6/2017: I’ve since figured this out. At high serial rates (500000+), if the serial line is saturated the Arduino can buffer an entire header in the time before the last byte of the previous frame is latched and interrupts are disabled. The most recent Adalight-FastLED code flushes the serial buffer after each frame to avoid this flickering.
Note that as of this writing Prismatik has no protections again serial buffering. If the capture rate is faster than the saturation level of the serial line, frames will be buffered on the PC-side of the connection, causing the ambilight to lag several seconds behind the monitor. So even with a high framerate, those numbers likely represent the ambilight running in an unusable state. Perhaps figuring out why the framerate recovers at all can be the topic of a future post.
PREDICTION #3: FRAMERATE PEAK
This framerate peak will occur around the point that grab interval equals the combined frame time.
This turned out to be somewhat true, but difficult to measure exactly due to Prismatik’s inconsistent grab rate (more on that in a second).
Borrowing an equation from the calculations post, if we assume Prismatik follows the grab rate perfectly we can predict the max possible grab rate using this formula:
Where N is the number of LEDs (80), b is the baud rate, and TTotal is the total amount of time a new frame takes between the serial data and the LED data (WS2812B) in seconds. If we eliminate the LED data from the equation, we know TSerial, which would be the total frame time if we were using another LED type.
Conveniently, the time a single frame takes is also the lowest ideal grab rate setting (i.e. you shouldn’t capture more frames than you can physically send).
Ideal Grab Rates (80 LEDs)
|Baud Rate||Total Frame Time (ms)||Ideal Grab Interval (ms)||Measured Peak Grab Interval (ms)|
|57600||45.1||46||35 / 34|
|115200||23.8||24||27 - 20|
Here are the ideal grab rates for 80 LEDs at the baud rates I’m testing. If the total frame time is time it takes to send and render a new frame, the “ideal” grab rate is the frame time rounded up to the nearest whole number below (i.e. not exceeding) that rate. The third column of the table lists the grab intervals where the measured peak framerates occurred.
The peak framerate for 57600 baud occurred at a grab rate of 35 / 34 ms, which is over 10 ms lower than where it should have occurred. While for 115200 baud, where the peak was predicted at ~24, grab rates between ~27 and 20 produced the same peak framerate (31-33 fps).
For 250000 and 500000 baud the results are more in-line with predictions, peaking at 14 (predicted: 12.2) and 9 (predicted: 7.3) milliseconds, respectively.
With more consistent grab software, I’m confident that the results would be more in-line with their predicted values.
Inconsistent Grab Rates
I went into this test hoping to measure the actual framerates produced on my Adalight device, both to find the best settings for a high framerate and to validate my calculations. However, the most significant result from this test is something I didn’t expect at all: the grab rates in Prismatik do not appear to function properly.
For its framerate setting, Prismatik uses a “Grab Interval” which is user-configurable in milliseconds (1 – 1000). This works fairly-well for lower framerates (~10-30), but breaks down when trying to reach higher levels.
The ideal framerate produced by a given grab interval is reciprocal:
Where FIdeal is the ideal framerate produced by the given grab interval, and G is the grab interval in milliseconds.
This means that changes to the grab interval at large intervals produce tiny differences in framerate, while changes at small intervals produce huge differences. As an example, changing the grab rate from 100 to 101 produces a framerate change of less than 0.1 fps, while changing the grab rate from 5 to 6 produces a change of ~33 fps.
That is, of course, assuming the function is working properly. But from this test it doesn’t appear to be.
Here is a same graph of framerate vs. grab rate as above, now including the ideal framerate produced for a given grab interval (independent of baud rate):
Instead of following the curve smoothly, the data shows that the produced framerate appears to “step”. The framerate meets the ideal curve and then stays constant until it suddenly increases to meet the curve again. For this dataset this occurs clearly at three grab intervals:
- 48 to 47
- 32 to 31
- 20 to 19
At lower grab intervals the ideal rate, along with the hardware requirements to render the ideal rate, spike too dramatically to form any definitive conclusions. But it certainly seems like there is something wrong with how Prismatik is processing its grab rate.
I’ll open an issue on Patrick Siegler’s fork to see if he has any insight into what’s causing this. I’m unfortunately not familiar enough with Prismatik’s source code to go bug hunting.
I’m glad I took the time to measure the real-world version of the calculated max framerates. It seems as though my calculations check out, and I now have a definitive group of settings that should give me a relatively stable 60 frames per second.
More than that, it appears as if I found a significant bug in Prismatik. Regardless of the protocol or application, the software on both sides needs to be functioning properly to get consistent results.
UPDATE: January 19, 2019
Version 220.127.116.11 of Psieg’s fork apparently fixes the inconsistent grab rates bug, which was due to using Qt’s coarse precision timer that allows the timer interval to be adjusted up to 5% to align it with other timers that are expected to fire around the same time. See pull request #212 for more info.
The hardware setup for measuring is as follows:
The Arduino is on a solderless breadboard, with the following connections:
- Serial RX -> Logic Channel #1
- Digital Pin #6 (LED Data Out) -> Logic Channel #3
- Digital Pin #8 (FPS Latch) -> Logic Channel #2
- Arduino Ground -> Logic Ground
The software on the Arduino is exactly what my ambilight uses, with an added feature to toggle the #8 pin on LED latch. This makes it easy to measure the number of frames and is a necessary evil. For these tests though, the non-blocking ~3.4 µs delay from the FPS pin toggling should have a negligible effect on the framerate measurement.
Software and Methodology
As I’ve mentioned many times in other posts, I’m using Patrick Siegler’s unofficial fork of Prismatik as the PC capture software. For these tests I’m running version 18.104.22.168. The software for the logic analyzer is Saleae Logic, version 1.2.13.
With each measurement I’m varying serial baud rate and Prismatik’s “Grab Interval”. Everything else is held constant. The logic analyzer, which I’m using to take the actual measurements, is set to sample at a rate of 24 MS/s for three seconds.
Here are the rest of my Prismatik settings, for completeness:
- Number of LEDs: 80
- Over-Brightening: 0
- Scene-Luminosity Threshold: 3 (Minimum Luminosity Level)
- Overall Brightness: 100%
- Gamma Correction: 2.00
I’m also using the my color calibrated LED profile (RGB 1 / 0.67 / 0.33), to keep things as consistent as possible with my actual LED setup.
Here is the method for each grab interval sample:
- Change grab interval in Prismatik
- Check that the serial buffer is clear using the indicator light on the Arduino
- Turn on lights in Prismatik (start sending data)
- Using Saleae Logic, capture 3 seconds of data
- Turn off lights in Prismatik (stop sending data)
- After capture, measure rising edges of the “FPS Latch” channel for a one second duration to calculate framerate.
The framerate was measured for grab intervals 50 through 1 at the following baud rates:
After all samples were measured for a given baud rate, I closed Prismatik. The Adalight-FastLED code was updated for the new baud rate and uploaded to the Arduino Nano, while main.conf was edited to change the baud rate in Prismatik.
Originally I was going to take three measurements for each grab rate and then average them, but the readings for the grab rates above 10 ms were consistently within a range of +/- 1, so I decided to save time and do a single measurement for each rate.
Prismatik does some data-saving tricks where if the screen don’t change it won’t send a new frame. Because of this I made sure to keep the mouse moving during the capture time so that frames continued to be sent.