-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Configurable OSD refresh rate #11206
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: maintenance-9.x
Are you sure you want to change the base?
Conversation
Branch Targeting SuggestionYou've targeted the
If This is an automated suggestion to help route contributions to the appropriate branch. |
|
Can also be ported to 8.x.x (which I tested on). I can open another PR if needed. |
|
There won't be any more updates for 8.x. This would need to go in to 9.1. |
98b3377 to
6ab345c
Compare
|
I'm not sure updating all elements every cycle is a good idea. If there are a lot of elements. This could cause issues. Maybe instead of a timeout where all elements are updated. Maybe just set the number of elements to be updated each cycle. Maybe with a range of 1 to 10. 1 would be the current way, with a single element updated each cycle. 10 would be 10 elements per cycle. I think that would be plenty fast enough. With a 30 element OSD and this set to 10. All elements would be updating at ~21Hz. Thats more than fast enough. Setting to 5 and them updating at ~10Hz would be fast enough too. |
Yeah. This is the way. After a certain number of microseconds, it's going to be time for the PID task to run. Rather than the OSD task occasionally trying to take far more than the available time, it should consistently do what it can in the time available. Consistently do two or three elements each cycle. Same as you might work 8 hours per day. Not try to work 40 hours one day per week. |
|
I've tested this change on my SPEEDYBEEF405WING (9.0.0) using a layout populated with nearly all available OSD elements:
For testing purposes I used this code:static void osdDrawAllElements(void)
{
uint32_t startUs = micros();
uint8_t count = 0;
for (uint8_t element = 0; element < OSD_ITEM_COUNT; element++) {
if (osdDrawSingleElement(element)) count++;
}
osdDrawSingleElement(OSD_ARTIFICIAL_HORIZON);
if (osdConfig()->telemetry>0){
osdDisplayTelemetry();
}
uint32_t elapsedUs = micros() - startUs;
osdPerf.lastTimeUs = elapsedUs;
osdPerf.elementsDrawn = count;
if (elapsedUs > osdPerf.maxTimeUs) {
osdPerf.maxTimeUs = elapsedUs;
}
DEBUG_SET(DEBUG_OSD_PERF, 0, osdPerf.lastTimeUs);
DEBUG_SET(DEBUG_OSD_PERF, 1, osdPerf.maxTimeUs);
}Results I got:
With an intentionally overloaded layout, a full OSD frame render takes approximately 1-1.4ms, using 6-9% of the 16ms budget available at 62.5Hz (250Hz drawScreen() / DRAW_FREQ_DENOM)With a more realistic, moderate OSD setup, render time is ~0.4ms (2.5% of available time), which is effectively nothing from a performance perspective.
Based on these measurements, I believe that adding this CLI option will make OSD behavior clearer and easier to configure, reducing cases where users have to guess why the OSD seems to update slowly (as in already mentioned #9907). Setting its default value to The data shows that even with a large number of active elements, rendering time is not the limiting factor: even in the worst-case layout it is around 1 ms per frame
Fortunately, I’m not a microcontroller 😅 |
|
I agree with @sensei-hacker I've experienced some odd lag while in flight at times, when the OSD is highly populated and a number of other features are also active. It can ranging from the OSD itself just lagging, to other more critical systems being slowed. I'm not saying this isn't related to a problem somewhere else. But caution should be taken here. @hntirgeam Can I assume your testing was done on the bench, and not inflight ? |
|
@Jetrell, Testing was done on bench in armed state with GPS/PID/telemetry and active blackbox logging enabled. I understand the concern about CPU load with other features active. However, there's an issue with the batch approach: It increases CPU usage rather than reducing it. CPU Usage ComparisonBench measurements with 40 active OSD elements:Hz-based approach at 12Hz:
UPD: calculations above are NOT for 40 elements, but for MUCH MORE amount of elements mentioned in previous comment. For 40 elements, as said, its around 410μs per full frame.Batch approach with 5 elements/cycle (as suggested here #11207):
Batch approach with 10 elements/cycle:
The batch approach uses 30% more CPU (2.19% vs 1.68%) while providing a slower update rate (10.4Hz vs 12Hz). The batch approach calls the drawing function 62.5 times/second regardless of settings. Each call has overhead: The Hz-based approach calls the drawing function only as often as configured (12 times/second). Total time spent rendering is similar, but the batch approach adds 5x more function call overhead. User ConfigurationBatch approach: set elements_per_cycle = 5
Hz-based approach: set osd_framerate_hz = 12
The batch approach doesn't reduce total CPU time - it spreads the same work across more calls while adding overhead If a 1.4ms operation at 12Hz causes issues, a 350μs operation at 62.5Hz won't help - the total CPU time is higher. The safety mechanism is the -1 default value, which preserves current behavior. Users opt-in to higher refresh rates based on their needs. |
|
There is a big issue with your method though. It is not using the osdIncElementIndex function. This ensures that the correct next element is drawn. You are trying to draw everything, whether it is valid or not. |
The PID loop needs to run every 0.5 ms. The PID loop itself takes some time, so figure there is maybe 0.3ms - 0.4ms in between for everything else to run. 0.4ms is more than total time available for everything but PID in that loop. "Total average CPU usage" is not really a measurement of interest. That's just the amount of time the CPU is sitting idle, doing nothing. It is not helpful to block the PID loop for a while, then have the CPU idle for a while. What matters is that every task completes in time, every time. |
There are not 40 hours in a day, regardless of whether you're a microcontroller or a pineapple. Nothing can work for 40 hours within a 24 hour day. Similarly, you can't work for 1,400 us within the 500us interval of PID starts. (And the PID loop itself takes time, so there is about 300us - 400us between PID task runs). You can't lock up the MCU and block the PID loop three times in a row. "But later on I do nothing while the MCU is idle" doesn't make it okay to block essential flight functions. That's like saying "I wasn't speeding because after I drove 150 MPH, I parked the car for a while". Doing nothing later doesn't undo the damage. |
|
Yes, I realize that. I incorrectly assumed that INAV takes a slightly different approach to task scheduling. I am currently thinking about a proper solution to this problem. The batch processing approach seems more appealing now, but still less user friendly than specifying the OSD refresh rate as in Betaflight. |
|
If you think setting refresh rate is more user friendly, you could do that. Then the number of elements to update each time is just activeElements / (1/ rate ) or whatever. I wonder if there is a good way to communicate to users that you don't necessarily want to max this out. 🤔 I haven't closely at either code, but something else to be aware of: |



Summary
Implements configurable OSD refresh rate via
osd_framerate_hzCLI command.Fixes mentioned here behaviour.
Problem
INAVs element drawing updates only one element per frame. With many enabled elements (e.g., 30), each element updates at ~2Hz (62.5Hz / 30) which doesn't help when flying in harsh conditions. Only artificial horizon and telemetry updates as fast as possible (each frame)
Solution
osd_framerate_hzsetting (range:-1to60Hz)-1(default): Legacy (current) mode1-60: all elements update at specified Hz0: all elements update each frameVideo showing default behaviour,
osd_framerate_hz=12(default in betaflight) andosd_framerate_hz=0(each frame)osd_framerate_hz_hd.mp4