Skip to content
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

UART plugin recovers granularity if timing improves #1825

Open
wants to merge 6 commits into
base: 0.10
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@ Contributors:
Stefan Krüger, added APA102 support to the SPI Plugin
Tobi Schäfer, for the MacPort files
Stefan S, improved timing with monotonic clock
Markus, improved uartdmx plugin timing behaviour
33 changes: 33 additions & 0 deletions include/ola/Constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,39 @@ static const uint16_t OPEN_LIGHTING_ESTA_CODE = 0x7a70;
*/
static const uint32_t MAX_UNIVERSE = 0xffffffff;

/**
* @brief The minimum break time for a DMX frame (in microseconds).
*/
static const int DMX_BREAK_TIME_MIN = 1204;

/**
* @brief The maximum break time for a DMX frame is 1 second (in microseconds).
*/
static const int DMX_BREAK_TIME_MAX = 1000000;

/**
* @brief The time a single bit needs for sending (in microseconds).
*/
static const uint32_t DMX_TIME_PER_BIT = 4;

/**
* @brief The number of bits per slot.
*/
static const uint32_t DMX_BITS_PER_SLOT = 11;

/**
* @brief The number of slots the start code needs.
*/
static const uint32_t DMX_SLOT_START_CODE = 1;

/**
* @brief The time in microseconds a single DMX universe needs on its own:
* time per bit * bits per slot * (slots per universe + slot for start code).
* The MAB, MALFT and BREAK not added here on purpose.
*/
static const uint32_t DMX_UNIVERSE_FRAME_TIME = DMX_TIME_PER_BIT
* DMX_BITS_PER_SLOT * (DMX_UNIVERSE_SIZE + DMX_SLOT_START_CODE);

} // namespace ola

#endif // INCLUDE_OLA_CONSTANTS_H_
112 changes: 91 additions & 21 deletions plugins/uartdmx/UartDmxThread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@
#include <math.h>
#include <unistd.h>
#include <string>
#include <algorithm>
#include "ola/Clock.h"
#include "ola/Constants.h"
#include "ola/Logging.h"
#include "ola/StringUtils.h"
#include "plugins/uartdmx/UartWidget.h"
Expand Down Expand Up @@ -72,11 +74,22 @@ bool UartDmxThread::WriteDMX(const DmxBuffer &buffer) {
* The method called by the thread
*/
void *UartDmxThread::Run() {
TimeStamp ts1, ts2;
Clock clock;
CheckTimeGranularity();
DmxBuffer buffer;

// basic frame time (without break): MAB + time for a DMX universe + MALFT
// the basic_frame_time is in microseconds
int basic_frame_time = DMX_MAB + DMX_UNIVERSE_FRAME_TIME + m_malft;

// the capping of max and min values is done to honor the standard
int basic_frame_time_capped = std::max(
std::min(basic_frame_time, DMX_BREAK_TIME_MAX),
DMX_BREAK_TIME_MIN);

// converts the frame time into milliseconds for later use
m_frame_time = static_cast<int>(
floor(basic_frame_time_capped / static_cast<double>(ONE_THOUSAND)));

// Setup the widget
if (!m_widget->IsOpen())
m_widget->SetupOutput();
Expand All @@ -93,46 +106,103 @@ void *UartDmxThread::Run() {
buffer.Set(m_buffer);
}

if (!m_widget->SetBreak(true))
goto framesleep;
WriteDMXToUART(buffer);
}
return NULL;
}

if (m_granularity == GOOD)
usleep(m_breakt);
/**
* Write the DMX data to the actual UART interface.
*/
void UartDmxThread::WriteDMXToUART(const DmxBuffer &buffer) {
TimeStamp ts1;
Clock clock;

if (!m_widget->SetBreak(false))
goto framesleep;
// ensures that ts1 has a valid value for the framesleep
// if break condition cannot be locked/released
clock.CurrentMonotonicTime(&ts1);

if (m_granularity == GOOD)
usleep(DMX_MAB);
if (!m_widget->SetBreak(true)) {
FrameSleep(ts1);
return;
}

if (!m_widget->Write(buffer))
goto framesleep;
if (m_granularity == GOOD)
usleep(m_breakt);

framesleep:
// Sleep for the remainder of the DMX frame time
usleep(m_malft);
if (!m_widget->SetBreak(false)) {
FrameSleep(ts1);
return;
}
return NULL;

// timestamp for the elapsed time calculation during the framesleep
clock.CurrentMonotonicTime(&ts1);

if (m_granularity == GOOD)
usleep(DMX_MAB);

m_widget->Write(buffer);

FrameSleep(ts1);
}

/**
* Sleeps for the rest of the frame time and
* tries to recover the granularity if needed.
*/
void UartDmxThread::FrameSleep(const TimeStamp &ts1) {
TimeStamp ts2, ts3;
Clock clock;

clock.CurrentMonotonicTime(&ts2);
TimeInterval elapsed = ts2 - ts1;

if (m_granularity == GOOD) {
// Sleep for at least the remaining frame time to ensure
// we start the next frame afresh.
while (elapsed.InMilliSeconds() < m_frame_time) {
usleep(ONE_THOUSAND);
clock.CurrentMonotonicTime(&ts2);
elapsed = ts2 - ts1;
}
} else {
// See if we can drop out of bad mode.
usleep(ONE_THOUSAND);
clock.CurrentMonotonicTime(&ts3);
TimeInterval interval = ts3 - ts2;

if (interval.InMilliSeconds() <= BAD_GRANULARITY_LIMIT) {
m_granularity = GOOD;
OLA_INFO << "Switching back from BAD to GOOD granularity"
<< " for UART thread";
}

elapsed = ts3 - ts1;
// Sleep for at least the remaining frame time to ensure
// we start the next frame afresh.
while (elapsed.InMilliSeconds() < m_frame_time) {
clock.CurrentMonotonicTime(&ts2);
elapsed = ts2 - ts1;
}
}
}

/**
* Check the granularity of usleep.
*/
void UartDmxThread::CheckTimeGranularity() {
TimeStamp ts1, ts2;
Clock clock;
/** If sleeping for 1ms takes longer than this, don't trust
* usleep for this session
*/
const int threshold = 3;

clock.CurrentMonotonicTime(&ts1);
usleep(1000);
clock.CurrentMonotonicTime(&ts2);

TimeInterval interval = ts2 - ts1;
m_granularity = interval.InMilliSeconds() > threshold ? BAD : GOOD;

// See if our 1ms sleep was accurate to within BAD_GRANULARITY_LIMIT
m_granularity = (interval.InMilliSeconds() > BAD_GRANULARITY_LIMIT) ?
BAD : GOOD;
OLA_INFO << "Granularity for UART thread is "
<< (m_granularity == GOOD ? "GOOD" : "BAD");
}
Expand Down
13 changes: 13 additions & 0 deletions plugins/uartdmx/UartDmxThread.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

#include "ola/DmxBuffer.h"
#include "ola/thread/Thread.h"
#include "ola/Clock.h"

namespace ola {
namespace plugin {
Expand All @@ -49,11 +50,23 @@ class UartDmxThread : public ola::thread::Thread {
DmxBuffer m_buffer;
ola::thread::Mutex m_term_mutex;
ola::thread::Mutex m_buffer_mutex;
unsigned int m_frame_time;

void CheckTimeGranularity();

void FrameSleep(const TimeStamp &ts1);
void WriteDMXToUART(const DmxBuffer &buffer);

static const uint32_t DMX_MAB = 16;

/**
* If the difference between the time interval actually slept and
* the intended one exceeds this limit, don't trust the function usleep
* for this frame. See if we can recover on the next loop.
* The limit is in microseconds.
*/
static const uint32_t BAD_GRANULARITY_LIMIT = 3;

DISALLOW_COPY_AND_ASSIGN(UartDmxThread);
};
} // namespace uartdmx
Expand Down