Skip to content

Commit

Permalink
Allow to sleep instead of busy waiting when limiting refresh rate
Browse files Browse the repository at this point in the history
On single core boards, busy waiting renders the system unresponsive.
We add the --led-no-busy-waiting flag, to allow the process to sleep
instead of busy waiting, thereby freeing up CPU cycles for other
processes at the cost of slightly less accurate frame timing.

On a Raspberry Pi 3B on two 32x32 panels, busy waiting keeps a core
98% busy. Sleeping reduces it to 14%.

On a Raspberry Zero on two 32x32 panels, busy waiting keeps the single
core 95% busy and gives an unresponsive system. Sleeping reduces it to
32% and keeps the system responsive.

Sleeping degrades the timing accuracy a bit, but is still good enough
by a far margin for many uses. The following numbers are for driving
two 32x32 panels and averaging the refresh rate across a minute (the
1kHz refresh rate limit is of course not reached, and is only there to
make sure that the relevant code path is taken):

                                                   +--------------+-----------+
                                                   | busy waiting | sleeping  |
+--------------------------------------------------+--------------+-----------+
| Raspberry Pi 3B, limit 70Hz, hardw. pulsing      |  70.000Hz    |  69.977Hz |
| Raspberry Pi 3B, limit 70Hz, no hardw. pulsing   |  70.000Hz    |  69.977Hz |
| Raspberry Pi 3B, limit 1kHz, hardw. pulsing      | 430.824Hz    | 430.855Hz |
| Raspberry Pi 3B, limit 1kHz, no hardw. pulsing   | 365.498Hz    | 365.456Hz |
| Raspberry Pi Zero, limit 70Hz, hardw. pulsing    |  69.986Hz    |  69.643Hz |
| Raspberry Pi Zero, limit 70Hz, no hardw. pulsing |  69.986Hz    |  69.638Hz |
| Raspberry Pi Zero, limit 1kHz, hardw. pulsing    | 263.877Hz    | 264.538Hz |
| Raspberry Pi Zero, limit 1kHz, no hardw. pulsing | 233.166Hz    | 231.650Hz |
+--------------------------------------------------+--------------+-----------+
  • Loading branch information
Christian Aistleitner committed Jul 2, 2023
1 parent a3eea99 commit ea26cd7
Show file tree
Hide file tree
Showing 8 changed files with 57 additions and 8 deletions.
6 changes: 6 additions & 0 deletions include/led-matrix-c.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
};

/**
Expand Down
4 changes: 4 additions & 0 deletions include/led-matrix.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions lib/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
5 changes: 5 additions & 0 deletions lib/gpio.cc
Original file line number Diff line number Diff line change
Expand Up @@ -814,4 +814,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
2 changes: 2 additions & 0 deletions lib/gpio.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 2 additions & 0 deletions lib/led-matrix-c.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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
}

Expand Down
26 changes: 20 additions & 6 deletions lib/led-matrix.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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_;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
13 changes: 11 additions & 2 deletions lib/options-initialize.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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=<name> : Needed to initialize special panels. Supported: 'FM6126A', 'FM6127'\n",
"\t--led-panel-type=<name> : 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(),
Expand All @@ -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 "
Expand Down

0 comments on commit ea26cd7

Please sign in to comment.