diff --git a/README.md b/README.md index 25da5439f..f6dbd27d2 100644 --- a/README.md +++ b/README.md @@ -384,6 +384,20 @@ 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-no-busy-waiting : Don't use busy waiting when limiting refresh rate. +``` + +This allows to switch from busy waiting to sleep waiting when limiting the +refresh rate (`--led-limit-refresh`). + +By default, refresh rate limiting uses busy waiting, which is CPU intensive but +gives most accurate timings. This is fine for multi-core boards. + +On single core boards (e.g.: Raspberry Pi Zero) busy waiting makes the system +unresponsive for other/background tasks. There, sleep waiting improves the +system's responsiveness at the cost of slightly less accurate timings. + ``` --led-scan-mode=<0..1> : 0 = progressive; 1 = interlaced (Default: 0). ``` @@ -449,7 +463,7 @@ to debug if it has something to do with the sound subsystem (see Troubleshooting section). This is really only recommended for debugging; typically you actually want the hardware pulses as it results in a much more stable picture. - + ``` --led-no-drop-privs : Don't drop privileges from 'root' after initializing the hardware. diff --git a/include/led-matrix-c.h b/include/led-matrix-c.h index e6907311b..15ad90c9c 100644 --- a/include/led-matrix-c.h +++ b/include/led-matrix-c.h @@ -152,6 +152,12 @@ struct RGBLedMatrixOptions { * to keep a constant refresh rate. <= 0 for no limit. */ int limit_refresh_rate_hz; /* Corresponding flag: --led-limit-refresh */ + + /* Sleep instead of busy waiting when limiting refresh rate. This gives + * slightly less accurate frame timing, but lets the CPU work on other + * processes when waiting and renders single core boards more responsive. + */ + bool disable_busy_waiting; /* Corresponding flag: --led-busy-waiting */ }; /** diff --git a/include/led-matrix.h b/include/led-matrix.h index 5e401fe9c..ed7960efe 100644 --- a/include/led-matrix.h +++ b/include/led-matrix.h @@ -155,6 +155,10 @@ class RGBMatrix : public Canvas { // 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 + + // Sleep instead of busy wait to free CPU cycles but get slightly less + // accurate frame timing. + bool disable_busy_waiting; // Flag: --led-busy-waiting }; // Factory to create a matrix. Additional functionality includes dropping diff --git a/lib/Makefile b/lib/Makefile index 335936388..5664b1d31 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -129,6 +129,13 @@ HARDWARE_DESC?=regular # Flag: --led-limit-refresh #DEFINES+=-DFIXED_FRAME_MICROSECONDS=5000 +# When limiting refrash rate, a CPU core is busy waiting to get accurate +# timing. On single board systems, this results in an unresponsive system. +# By disabling busy waiting, CPU cycles are freed up, leading to a more +# responsive system at the cost of slightly less accurate frame timing. +# Flag: --led-no-busy-waiting +#DEFINES+=-DDISABLE_BUSY_WAITING + # Enable wide 64 bit GPIO offered with the compute module. # This will use more memory to internally represent the frame buffer, so # caches can't be utilized as much. diff --git a/lib/gpio.cc b/lib/gpio.cc index bde67915f..455b6becb 100644 --- a/lib/gpio.cc +++ b/lib/gpio.cc @@ -853,4 +853,9 @@ uint32_t GetMicrosecondCounter() { return epoch_usec & 0xFFFFFFFF; } +// For external use, e.g. to lessen busy waiting. +void SleepMicroseconds(long t) { + Timers::sleep_nanos(t * 1000); +} + } // namespace rgb_matrix diff --git a/lib/gpio.h b/lib/gpio.h index 2114b1834..b9927bc8b 100644 --- a/lib/gpio.h +++ b/lib/gpio.h @@ -144,6 +144,8 @@ class PinPulser { // if possible and a terrible slow fallback otherwise. uint32_t GetMicrosecondCounter(); +void SleepMicroseconds(long); + } // end namespace rgb_matrix #endif // RPI_GPIO_INGERNALH diff --git a/lib/led-matrix-c.cc b/lib/led-matrix-c.cc index 74df44e03..4e62af913 100644 --- a/lib/led-matrix-c.cc +++ b/lib/led-matrix-c.cc @@ -88,6 +88,7 @@ static struct RGBLedMatrix *led_matrix_create_from_options_optional_edit( OPT_COPY_IF_SET(pixel_mapper_config); OPT_COPY_IF_SET(panel_type); OPT_COPY_IF_SET(limit_refresh_rate_hz); + OPT_COPY_IF_SET(disable_busy_waiting); #undef OPT_COPY_IF_SET } @@ -134,6 +135,7 @@ static struct RGBLedMatrix *led_matrix_create_from_options_optional_edit( ACTUAL_VALUE_BACK_TO_OPT(pixel_mapper_config); ACTUAL_VALUE_BACK_TO_OPT(panel_type); ACTUAL_VALUE_BACK_TO_OPT(limit_refresh_rate_hz); + ACTUAL_VALUE_BACK_TO_OPT(disable_busy_waiting); #undef ACTUAL_VALUE_BACK_TO_OPT } diff --git a/lib/led-matrix.cc b/lib/led-matrix.cc index 359071a79..70133c20e 100644 --- a/lib/led-matrix.cc +++ b/lib/led-matrix.cc @@ -122,9 +122,10 @@ class RGBMatrix::Impl::UpdateThread : public Thread { public: UpdateThread(GPIO *io, FrameCanvas *initial_frame, int pwm_dither_bits, bool show_refresh, - int limit_refresh_hz) + int limit_refresh_hz, bool allow_busy_waiting) : io_(io), show_refresh_(show_refresh), target_frame_usec_(limit_refresh_hz < 1 ? 0 : 1e6/limit_refresh_hz), + allow_busy_waiting_(allow_busy_waiting), running_(true), current_frame_(initial_frame), next_frame_(NULL), requested_frame_multiple_(1) { @@ -199,8 +200,13 @@ class RGBMatrix::Impl::UpdateThread : public Thread { ++low_bit_sequence; if (target_frame_usec_) { - while ((GetMicrosecondCounter() - start_time_us) < target_frame_usec_) { - // busy wait. We have our dedicated core, so ok to burn cycles. + if (allow_busy_waiting_) { + while ((GetMicrosecondCounter() - start_time_us) < target_frame_usec_) { + // busy wait. We have our dedicated core, so ok to burn cycles. + } + } else { + long spent_us = GetMicrosecondCounter() - start_time_us; + SleepMicroseconds(target_frame_usec_ - spent_us); } } @@ -245,6 +251,7 @@ class RGBMatrix::Impl::UpdateThread : public Thread { GPIO *const io_; const bool show_refresh_; const uint32_t target_frame_usec_; + const bool allow_busy_waiting_; uint32_t start_bit_[4]; Mutex running_mutex_; @@ -314,9 +321,14 @@ RGBMatrix::Options::Options() : pixel_mapper_config(NULL), panel_type(NULL), #ifdef FIXED_FRAME_MICROSECONDS - limit_refresh_rate_hz(1e6 / FIXED_FRAME_MICROSECONDS) + limit_refresh_rate_hz(1e6 / FIXED_FRAME_MICROSECONDS), +#else + limit_refresh_rate_hz(0), +#endif +#ifdef DISABLE_BUSY_WAITING + disable_busy_waiting(true) #else - limit_refresh_rate_hz(0) + disable_busy_waiting(false) #endif { // Nothing to see here. @@ -348,6 +360,7 @@ static void PrintOptions(const RGBMatrix::Options &o) { P_STR(pixel_mapper_config); P_STR(panel_type); P_INT(limit_refresh_rate_hz); + P_BOOL(disable_busy_waiting); #undef P_INT #undef P_STR #undef P_BOOL @@ -469,7 +482,8 @@ bool RGBMatrix::Impl::StartRefresh() { if (updater_ == NULL && io_ != NULL) { updater_ = new UpdateThread(io_, active_, params_.pwm_dither_bits, params_.show_refresh_rate, - params_.limit_refresh_rate_hz); + params_.limit_refresh_rate_hz, + !params_.disable_busy_waiting); // 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. diff --git a/lib/options-initialize.cc b/lib/options-initialize.cc index 3c2abd625..46c90a319 100644 --- a/lib/options-initialize.cc +++ b/lib/options-initialize.cc @@ -209,6 +209,12 @@ static bool FlagInit(int &argc, char **&argv, continue; } + bool allow_busy_waiting = !mopts->disable_busy_waiting; + if (ConsumeBoolFlag("busy-waiting", it, &allow_busy_waiting)) { + mopts->disable_busy_waiting = !allow_busy_waiting; + continue; + } + bool request_help = false; if (ConsumeBoolFlag("help", it, &request_help) && request_help) { // In that case, we pretend to have failure in parsing, which will @@ -338,7 +344,8 @@ void PrintMatrixFlags(FILE *out, const RGBMatrix::Options &d, "\t--led-pwm-dither-bits=<0..2> : Time dithering of lower bits " "(Default: 0)\n" "\t--led-%shardware-pulse : %sse hardware pin-pulse generation.\n" - "\t--led-panel-type= : Needed to initialize special panels. Supported: 'FM6126A', 'FM6127'\n", + "\t--led-panel-type= : Needed to initialize special panels. Supported: 'FM6126A', 'FM6127'\n" + "\t--led-%sbusy-waiting : %sse busy waiting when limiting refresh rate.\n", d.hardware_mapping, d.rows, d.cols, d.chain_length, d.parallel, (int) muxers.size(), CreateAvailableMultiplexString(muxers).c_str(), @@ -350,7 +357,9 @@ void PrintMatrixFlags(FILE *out, const RGBMatrix::Options &d, d.inverse_colors ? "no-" : "", d.inverse_colors ? "off" : "on", d.pwm_lsb_nanoseconds, !d.disable_hardware_pulsing ? "no-" : "", - !d.disable_hardware_pulsing ? "Don't u" : "U"); + !d.disable_hardware_pulsing ? "Don't u" : "U", + !d.disable_busy_waiting ? "no-" : "", + !d.disable_busy_waiting ? "Don't u" : "U"); fprintf(out, "\t--led-slowdown-gpio=<0..4>: " "Slowdown GPIO. Needed for faster Pis/slower panels "