Skip to content

Commit 33e51f7

Browse files
dkulpclaude
andcommitted
fix(BBShiftPanel): prime the SMEM ring before starting a PWM frame
6543a19 moved the self-PWM panel types onto the shared-memory ring along with the shift panels. The ring delivers every byte - the produced and consumed counters stay exact and aligned to the PRU's 48 byte load, which is what that commit verified - but it delivers them late. Measured on FM6363C hardware, the PRU entered RINGWAIT about seven times per frame for 40-50us each. That is harmless for a shift panel, where the same PRU drives both the data and the row address. A self-PWM panel is different: it refreshes from its own engine while its row scan free runs, so a gap in the shift out lets the scan walk away from the data. Roughly 300us of gaps per frame against a ~13.8us row period is more than a full pass of drift, which is why the image slid vertically by a different amount every frame while every byte-level check came back clean. The ring was empty at the start of every frame (produced == consumed there), so the dominant term was the pump's wakeup latency: it polled the frame sequence on a 500us sleep, and the DATA command went out before any bytes had been written. So wake the pump from PrepDataPWM rather than having it poll, hold the DATA command until the pump reports the ring full, and back off in 20us steps instead of 150us once it is running. The stall count is unchanged afterwards but every RINGWAIT entry now resolves on the first reload of the producer counter, so the wait drops from ~300us per frame to nothing measurable. Checked on 8 and 16 output capes driving an 80x40 1/10 scan panel. The shift path is untouched and was re-checked with 64x32 1/16 scan panels on a 16 output cape. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 18bd134 commit 33e51f7

2 files changed

Lines changed: 42 additions & 6 deletions

File tree

src/non-gpl/BBShiftPanel/BBShiftPanel.cpp

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -992,22 +992,40 @@ void BBShiftPanelManager::runPumpThread() {
992992
// command so the byte flow stays in step with the commands
993993
while (m_pumpRunning) {
994994
if (m_pumpedSeq == m_pumpSeq.load(std::memory_order_acquire)) {
995-
struct timespec ts = { 0, 500000 };
996-
nanosleep(&ts, nullptr);
995+
// PrepDataPWM wakes this the moment a frame is published; the
996+
// timeout is only a backstop so shutdown cannot wedge here.
997+
// Polling on a 500us sleep used to leave the PRU shifting
998+
// against an empty ring for most of that wakeup latency.
999+
std::unique_lock<std::mutex> lk(m_pumpMutex);
1000+
m_pumpCV.wait_for(lk, std::chrono::microseconds(200), [this] {
1001+
return !m_pumpRunning || m_pumpSeq.load(std::memory_order_acquire) != m_pumpedSeq;
1002+
});
9971003
continue;
9981004
}
9991005
++m_pumpedSeq;
10001006
uint8_t* src = m_frontBuffer.load(std::memory_order_acquire);
10011007
uint32_t srcOff = 0;
1008+
bool primed = false;
10021009
while (srcOff < m_frameBytes && m_pumpRunning) {
10031010
uint32_t n = m_ring.write(src + srcOff, std::min(PUMP_BLOCK_SIZE, m_frameBytes - srcOff));
10041011
if (n == 0) {
1005-
struct timespec ts = { 0, 150000 };
1012+
// the ring holds as much as it can - the PRU may start.
1013+
// Back off in small steps: at the drain rate a 150us sleep
1014+
// was long enough to open a gap the panel could see.
1015+
if (!primed) {
1016+
primed = true;
1017+
m_pumpPrimed.store(m_pumpedSeq, std::memory_order_release);
1018+
}
1019+
struct timespec ts = { 0, 20000 };
10061020
nanosleep(&ts, nullptr);
10071021
continue;
10081022
}
10091023
srcOff += n;
10101024
}
1025+
// a frame smaller than the ring never fills it
1026+
if (!primed) {
1027+
m_pumpPrimed.store(m_pumpedSeq, std::memory_order_release);
1028+
}
10111029
}
10121030
return;
10131031
}
@@ -1306,10 +1324,20 @@ void BBShiftPanelManager::PrepDataPWM() {
13061324
: "memory");
13071325
}
13081326

1309-
// hand the frame to the pump thread; the PRU is paced by the ring so the
1310-
// DATA command can be queued immediately
1327+
// hand the frame to the pump thread
13111328
m_frontBuffer.store(buf, std::memory_order_release);
1312-
m_pumpSeq.fetch_add(1, std::memory_order_release);
1329+
uint32_t seq = m_pumpSeq.fetch_add(1, std::memory_order_release) + 1;
1330+
m_pumpCV.notify_one();
1331+
// Hold the DATA command until the ring is primed. Starting the PRU
1332+
// against an empty ring left it in RINGWAIT for the pump's wakeup
1333+
// latency, and an FM6363C reads that gap as its row scan running away
1334+
// from the data. Filling the ring takes ~65us; the loop bound is only a
1335+
// backstop so a stopped pump cannot hang the output thread.
1336+
for (int i = 0; i < 4000 && m_pumpRunning && bgThreadsRunning &&
1337+
m_pumpPrimed.load(std::memory_order_acquire) != seq;
1338+
++i) {
1339+
std::this_thread::yield();
1340+
}
13131341

13141342
pruData->numBlocks = rowLen / 16;
13151343
pruData->numRows = numRows;

src/non-gpl/BBShiftPanel/BBShiftPanel.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,14 @@ class BBShiftPanelManager {
254254
// sequence bump so the byte flow stays in step with the commands
255255
std::atomic<uint32_t> m_pumpSeq{ 0 };
256256
uint32_t m_pumpedSeq = 0;
257+
// A self-PWM panel refreshes from its own engine while its row scan free
258+
// runs, so a gap in the shift out shows up as the image drifting
259+
// vertically rather than as a glitch. The frame is therefore not handed
260+
// to the PRU until the pump has the ring as full as it will go, and the
261+
// pump is woken directly rather than found by polling.
262+
std::mutex m_pumpMutex;
263+
std::condition_variable m_pumpCV;
264+
std::atomic<uint32_t> m_pumpPrimed{ 0 };
257265
uint32_t m_frameBytes = 0;
258266
BBBPruSMEMRing m_ring;
259267
bool m_heapBuffers = false;

0 commit comments

Comments
 (0)