Skip to content

Commit

Permalink
Provide --led-limit-refresh to fix refresh rate; helps smooth flicker…
Browse files Browse the repository at this point in the history
… on loaded system.

This used to be a compile-time option FIXED_FRAME_MICROSECONDS but
it is very useful to tweak at runtime.

Issues #276 #458 #467 #478 #483 #495 #551 #556 #571 #626 #651 #710
  • Loading branch information
hzeller committed Mar 18, 2020
1 parent 53ff973 commit 1ba867b
Show file tree
Hide file tree
Showing 10 changed files with 92 additions and 16 deletions.
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,42 @@ to `--led-pwm-bits`, `--led-pwm-lsb-nanoseconds` and `--led-pwm-dither-bits`.
If you are tweaking these parameters, showing the refresh rate can be a
useful tool.

```
--led-limit-refresh=<Hz> : Limit refresh rate to this frequency in Hz. Useful to keep a
constant refresh rate on loaded system. 0=no limit. Default: 0
```

This allows to limit the refresh rate to a particular frequency to approach
a fixed refresh rate.

This can be used to mitigate some situations in which you have a faint flicker,
which can happen due to hardware events (network access)
or other situations such as other IO or heavy memory access by other
processes. Also when you see wildly changing refresh freuqncies with
`--led-show-refresh`.

You trade a slightly slower refresh rate and display brightness for less
visible flicker situations.

For this to calibrate, run your program for a while with --led-show-refresh
and watch the line that shows the current refresh rate and minimum refresh
rate observed. So wait a while until that value doesn't
change anymore (e.g. a minute, so that you catch tasks that happen once
a minute, such as ntp updated).
Use this as a guidance what value to choose with `--led-limit-refresh`.

The refresh rate will now be adapted to always reach this value
between frames, so faster refreshes will be slowed down, but the occasional
delayed frame will fit into the time-window as well, thus reducing visible
brightness fluctuations.

You can play with value a little and reduce until you find a good balance
between refresh rate and flicker suppression.

Use this also if you want to have a stable baseline refresh rate when using
the vsync-multiple flag `-V` in the [led-image-viewer] or
[video-viewer] utility programs.

```
--led-scan-mode=<0..1> : 0 = progressive; 1 = interlaced (Default: 0).
```
Expand Down Expand Up @@ -713,6 +749,8 @@ things, like this installation by Dirk in Scharbeutz, Germany:

![](./img/user-action-shot.jpg)

[led-image-viewer]: ./utils#image-viewer
[video-viewer]: ./utils#video-viewer
[matrix64]: ./img/chained-64x64.jpg
[sparkfun]: https://www.sparkfun.com/products/12584
[ada]: http://www.adafruit.com/product/1484
Expand Down
5 changes: 5 additions & 0 deletions include/led-matrix-c.h
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,11 @@ struct RGBLedMatrixOptions {
unsigned show_refresh_rate:1; /* Corresponding flag: --led-show-refresh */
// unsigned swap_green_blue:1; /* deprecated, use led_sequence instead */
unsigned inverse_colors:1; /* Corresponding flag: --led-inverse */

/* Limit refresh rate of LED panel. This will help on a loaded system
* to keep a constant refresh rate. <= 0 for no limit.
*/
int limit_refresh_rate_hz; /* Corresponding flag: --led-limit-refresh */
};

/**
Expand Down
4 changes: 4 additions & 0 deletions include/led-matrix.h
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,10 @@ class RGBMatrix : public Canvas {
// Panel type. Typically an empty string or NULL, but some panels need
// a particular initialization sequence, so this is used for that.
const char *panel_type; // Flag: --led-panel-type

// Limit refresh rate of LED panel. This will help on a loaded system
// to keep a constant refresh rate. <= 0 for no limit.
int limit_refresh_rate_hz; // Flag: --led-limit-refresh
};

// Create an RGBMatrix.
Expand Down
2 changes: 1 addition & 1 deletion lib/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,6 @@ HARDWARE_DESC?=regular
# Transformers will be removed from the code-base soon to avoid confusion.
#DEFINES+=-DINCLUDE_DEPRECATED_TRANSFORMERS

# --- EXPERIMENTAL --
# This allows to fix the refresh rate to a particular refresh time in
# microseconds.
#
Expand Down Expand Up @@ -134,6 +133,7 @@ HARDWARE_DESC?=regular
# You can play with value a little and reduce until you find a good balance
# between refresh rate (which is reduce the higher this value is) and
# flicker suppression (which is better with higher values).
# Flag: --led-limit-refresh
#DEFINES+=-DFIXED_FRAME_MICROSECONDS=5000

# ---- Pinout options for hardware variants; usually no change needed here ----
Expand Down
2 changes: 2 additions & 0 deletions lib/led-matrix-c.cc
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ struct RGBLedMatrix *led_matrix_create_from_options(
OPT_COPY_IF_SET(led_rgb_sequence);
OPT_COPY_IF_SET(pixel_mapper_config);
OPT_COPY_IF_SET(panel_type);
OPT_COPY_IF_SET(limit_refresh_rate_hz);
#undef OPT_COPY_IF_SET
}

Expand Down Expand Up @@ -115,6 +116,7 @@ struct RGBLedMatrix *led_matrix_create_from_options(
ACTUAL_VALUE_BACK_TO_OPT(led_rgb_sequence);
ACTUAL_VALUE_BACK_TO_OPT(pixel_mapper_config);
ACTUAL_VALUE_BACK_TO_OPT(panel_type);
ACTUAL_VALUE_BACK_TO_OPT(limit_refresh_rate_hz);
#undef ACTUAL_VALUE_BACK_TO_OPT
}

Expand Down
32 changes: 23 additions & 9 deletions lib/led-matrix.cc
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,11 @@ using namespace internal;
class RGBMatrix::UpdateThread : public Thread {
public:
UpdateThread(GPIO *io, FrameCanvas *initial_frame,
int pwm_dither_bits, bool show_refresh)
: io_(io), show_refresh_(show_refresh), running_(true),
int pwm_dither_bits, bool show_refresh,
int limit_refresh_hz)
: io_(io), show_refresh_(show_refresh),
target_frame_usec_(limit_refresh_hz < 1 ? 0 : 1e6/limit_refresh_hz),
running_(true),
current_frame_(initial_frame), next_frame_(NULL),
requested_frame_multiple_(1) {
pthread_cond_init(&frame_done_, NULL);
Expand Down Expand Up @@ -127,19 +130,23 @@ class RGBMatrix::UpdateThread : public Thread {
++frame_count;
++low_bit_sequence;

#ifdef FIXED_FRAME_MICROSECONDS
while ((GetMicrosecondCounter() - start_time_us) < (uint32_t)FIXED_FRAME_MICROSECONDS) {
// busy wait.
if (target_frame_usec_) {
while ((GetMicrosecondCounter() - start_time_us) < target_frame_usec_) {
// busy wait. We have our dedicated core, so ok to burn cycles.
}
}
#endif

const uint32_t end_time_us = GetMicrosecondCounter();
if (show_refresh_) {
uint32_t usec = end_time_us - start_time_us;
printf("\b\b\b\b\b\b\b\b%6.1fHz", 1e6 / usec);
if (usec > largest_time && max_measure_enabled) {
largest_time = usec;
printf(" max: %uusec\b\b\b\b\b\b\b\b\b\b\b\b\b\b", largest_time);
const float lowest_hz = 1e6 / largest_time;
printf(" (lowest: %.1fHz)"
"\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b", lowest_hz);
} else {
// Don't measure at startup, as times will be janky.
max_measure_enabled = (end_time_us - initial_holdoff_start) > kHoldffTimeUs;
}
}
Expand Down Expand Up @@ -169,6 +176,7 @@ class RGBMatrix::UpdateThread : public Thread {

GPIO *const io_;
const bool show_refresh_;
const uint32_t target_frame_usec_;
uint32_t start_bit_[4];

Mutex running_mutex_;
Expand Down Expand Up @@ -235,7 +243,12 @@ RGBMatrix::Options::Options() :
#endif
led_rgb_sequence("RGB"),
pixel_mapper_config(NULL),
panel_type(NULL)
panel_type(NULL),
#ifdef FIXED_FRAME_MICROSECONDS
limit_refresh_rate_hz(1e6 / FIXED_FRAME_MICROSECONDS)
#else
limit_refresh_rate_hz(0)
#endif
{
// Nothing to see here.
}
Expand Down Expand Up @@ -345,7 +358,8 @@ void RGBMatrix::SetGPIO(GPIO *io, bool start_thread) {
bool RGBMatrix::StartRefresh() {
if (updater_ == NULL && io_ != NULL) {
updater_ = new UpdateThread(io_, active_, params_.pwm_dither_bits,
params_.show_refresh_rate);
params_.show_refresh_rate,
params_.limit_refresh_rate_hz);
// If we have multiple processors, the kernel
// jumps around between these, creating some global flicker.
// So let's tie it to the last CPU available.
Expand Down
6 changes: 6 additions & 0 deletions lib/options-initialize.cc
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,9 @@ static bool FlagInit(int &argc, char **&argv,
if (ConsumeIntFlag("row-addr-type", it, end,
&mopts->row_address_type, &err))
continue;
if (ConsumeIntFlag("limit-refresh", it, end,
&mopts->limit_refresh_rate_hz, &err))
continue;
if (ConsumeBoolFlag("show-refresh", it, &mopts->show_refresh_rate))
continue;
if (ConsumeBoolFlag("inverse", it, &mopts->inverse_colors))
Expand Down Expand Up @@ -391,6 +394,8 @@ void PrintMatrixFlags(FILE *out, const RGBMatrix::Options &d,
"\t--led-row-addr-type=<0..4>: 0 = default; 1 = AB-addressed panels; 2 = direct row select; 3 = ABC-addressed panels; 4 = ABC Shift + DE direct "
"(Default: 0).\n"
"\t--led-%sshow-refresh : %show refresh rate.\n"
"\t--led-limit-refresh=<Hz> : Limit refresh rate to this frequency in Hz. Useful to keep a\n"
"\t constant refresh rate on loaded system. 0=no limit. Default: %d\n"
"\t--led-%sinverse "
": Switch if your matrix has inverse colors %s.\n"
"\t--led-rgb-sequence : Switch if your matrix has led colors "
Expand All @@ -407,6 +412,7 @@ void PrintMatrixFlags(FILE *out, const RGBMatrix::Options &d,
available_mappers.c_str(),
d.pwm_bits, d.brightness, d.scan_mode,
d.show_refresh_rate ? "no-" : "", d.show_refresh_rate ? "Don't s" : "S",
d.limit_refresh_rate_hz,
d.inverse_colors ? "no-" : "", d.inverse_colors ? "off" : "on",
d.pwm_lsb_nanoseconds,
!d.disable_hardware_pulsing ? "no-" : "",
Expand Down
17 changes: 11 additions & 6 deletions utils/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,20 +68,20 @@ Options:
-O<streamfile> : Output to stream-file instead of matrix (Don't need to be root).
-C : Center images.
These options affect images following them on the command line:
These options affect images FOLLOWING them on the command line,
so it is possible to have different options for each image
-w<seconds> : Regular image: Wait time in seconds before next image is shown (default: 1.5).
-t<seconds> : For animations: stop after this time.
-l<loop-count> : For animations: number of loops through a full cycle.
-D<animation-delay-ms> : For animations: override the delay between frames given in the
gif/stream animation with this value. Use -1 to use default value.
-V<vsync-multiple> : For animation (expert): Only do frame vsync-swaps on multiples of refresh (default: 1)
(Tip: use --led-limit-refresh for stable rate)
Options affecting display of multiple images:
-f : Forever cycle through the list of files on the command line.
-s : If multiple images are given: shuffle.
Display Options:
-V<vsync-multiple> : Expert: Only do frame vsync-swaps on multiples of refresh (default: 1)
General LED matrix options:
<... all the --led- options>
Expand All @@ -106,7 +106,8 @@ sudo ./led-image-viewer -D16 animated.gif # Play animated gif, use 16ms frame
# If you want to have an even frame rate, that is depending on your
# refresh rate, use the following. Note, your refresh rate is dependent on
# factors such as chain length and rows; use --led-show-refresh to get an idea.
sudo ./led-image-viewer -D0 -V12 animated.gif # Frame rate = 1/12 refresh rate
# Then fix it with --led-limit-refresh
sudo ./led-image-viewer --led-limit-refresh=200 -D0 -V10 animated.gif # Frame rate = 1/12 refresh rate

sudo ./led-image-viewer -w3 foo.jpg bar.png # show two images, wait 3 seconds between. Stop.
sudo ./led-image-viewer -w3 foo.jpg -w2 bar.png baz.png # show images, wait 3 seconds after the first, 2 seconds after the second and third. Stop.
Expand Down Expand Up @@ -219,7 +220,11 @@ Options:
-O<streamfile> : Output to stream-file instead of matrix (don't need to be root).
-s <count> : Skip these number of frames in the beginning.
-c <count> : Only show this number of frames (excluding skipped frames).
-v : verbose.
-V<vsync-multiple> : Instead of native video framerate, playback framerate
is a fraction of matrix refresh. In particular with a stable refresh,
this can result in more smooth playback. Choose multiple for desired framerate.
(Tip: use --led-limit-refresh for stable rate)
-v : verbose; prints video metadata and other info.
-f : Loop forever.
General LED matrix options:
Expand Down
1 change: 1 addition & 0 deletions utils/led-image-viewer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ static int usage(const char *progname) {
"For animations: override the delay between frames given in the\n"
"\t gif/stream animation with this value. Use -1 to use default value.\n"
"\t-V<vsync-multiple> : For animation (expert): Only do frame vsync-swaps on multiples of refresh (default: 1)\n"
"\t (Tip: use --led-limit-refresh for stable rate)\n"

"\nOptions affecting display of multiple images:\n"
"\t-f : "
Expand Down
1 change: 1 addition & 0 deletions utils/video-viewer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ static int usage(const char *progname, const char *msg = nullptr) {
"\t-V<vsync-multiple> : Instead of native video framerate, playback framerate\n"
"\t is a fraction of matrix refresh. In particular with a stable refresh,\n"
"\t this can result in more smooth playback. Choose multiple for desired framerate.\n"
"\t (Tip: use --led-limit-refresh for stable rate)\n"
"\t-v : verbose; prints video metadata and other info.\n"
"\t-f : Loop forever.\n");

Expand Down

0 comments on commit 1ba867b

Please sign in to comment.