GCC Code Coverage Report


Directory: ./
File: non-gpl/BBShiftPanel/BBShiftPanel.h
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 0 7 0.0%
Functions: 0 6 0.0%
Branches: 0 2 0.0%

Line Branch Exec Source
1 #pragma once
2 /*
3 * This file is part of the Falcon Player (FPP) and is Copyright (C)
4 * 2022 by the Falcon Player Developers.
5 *
6 * The Falcon Player (FPP) is free software, and is covered under
7 * multiple Open Source licenses. Please see the included 'LICENSES'
8 * file for descriptions of what files are covered by each license.
9 *
10 * This source file is covered under the CC-BY-ND as described in the
11 * included LICENSE.CC-BY-ND file. This file may be modified for
12 * personal use, but modified copies MAY NOT be redistributed in any form.
13 */
14
15 #include <atomic>
16 #include <queue>
17 #include "fpp-json-fwd.h"
18 #include <string>
19 #include <thread>
20 #include <utility>
21 #include <vector>
22
23 #include "channeloutput/ChannelOutput.h"
24 #include "util/BBBPruUtils.h"
25
26 #include "../../channeloutput/Matrix.h"
27 #include "../../channeloutput/PanelMatrix.h"
28
29 // structure of the data at the start of the PRU ram
30 // that the pru program expects to see
31 typedef struct {
32 // in the DDR shared with the PRU
33 uint32_t address_dma;
34
35 union {
36 struct {
37 volatile uint16_t command;
38 volatile uint16_t result;
39 } __attribute__((__packed__));
40 struct {
41 // Standard shift register based panels where the data is shifted out row by row and the PWM
42 // is handled by the PRU code via the OE pin
43 // write data length to start, 0xFFFF to abort. will be cleared when started
44 uint16_t pixelsPerStride;
45 uint16_t numStrides;
46
47 // 2 uint32_t for each stride
48 // first uint32_t is the brightness, number of clock ticks for on
49 // second uint32_t - bit 32 is flag to output black after this row, bits 25-31 is the address, lower 24 is extra "off" time
50 // (24 slots x 32 rows: enough split-pulse budget for 12 bit
51 // color on 1/32 scan panels; ends at 0x1808 in the PRU data
52 // RAM, clear of the config areas at 0x1DF8+)
53 uint32_t brightness[24 * 32 * 2];
54 } __attribute__((__packed__));
55 struct {
56 // Panels that handle the PWM themselves, all data is shifted out and the panel displays it automatically
57 uint16_t cmd;
58 uint8_t numBlocks;
59 uint8_t numRows;
60 uint16_t buffer[4]; // buffer to get registers aligned on boundary
61 // up to 6 registers (SM16380SH takes a sixth slot; the rest use
62 // five), up to 12 bytes per clock (16 outputs), 16 clocks each.
63 // Ends at 0x490 in the PRU data RAM, clear of the chip config at
64 // PWM_CHIP_CONFIG_OFFSET (0x1DF8).
65 uint8_t registers[6 * 12 * 16];
66 } __attribute__((__packed__));
67 } __attribute__((__packed__));
68 } __attribute__((__packed__)) BBShiftPanelData;
69
70 class BBShiftPanelOutput;
71
72 // One captured register profile for a PWM driver chip: the per-colour word
73 // lists plus the grammar needed to upload them. The built-in tables live in
74 // BBShiftPanel.cpp; an imported profile (see BBShiftPanelManager::
75 // buildRegisterProfile) fills the same shape so nothing downstream has to
76 // know where the words came from.
77 struct PWMChipSeqVariant {
78 int scan;
79 const uint16_t* r;
80 const uint16_t* g;
81 const uint16_t* b;
82 };
83
84 struct PWMChipSeq {
85 const uint16_t* r;
86 const uint16_t* g;
87 const uint16_t* b;
88 int len;
89 int slots; // 5, or 6 for a chip with an extra pre-commit word
90 uint16_t extraWord; // that word; only read when slots == 6
91 bool midLatch; // send the 11 clock LAT burst
92 uint8_t scanReg; // register address holding the scan row count
93 int defaultScan; // scan rate the tables above were captured at
94 const PWMChipSeqVariant* variants;
95 int variantCount;
96 };
97
98 // The PRUSS drives every panel output on the cape as ONE frame: a single
99 // buffer whose byte lanes are the cape's data pins, one stride schedule, one
100 // shared memory ring, one pair of PRU cores. So several LED Panel Matrix
101 // outputs on the same cape (a two sided display, a column either side of a
102 // door) cannot each own that hardware - they share it here and own only their
103 // own pixel mapping into the shared frame. Since the lane for a given output
104 // is fixed by the cape pinout (see BBShiftPanelOutput::buildScatterMap),
105 // members driving different outputs scatter into disjoint bytes of the shared
106 // buffer and one bit-plane pass converts the combined result.
107 class BBShiftPanelManager {
108 public:
109 static BBShiftPanelManager INSTANCE;
110
111 // Registers a configured output. The first member fixes the frame
112 // geometry; later ones must match it and must not re-use an output that
113 // is already driven. Returns false (having warned) if they do not.
114 bool addMember(BBShiftPanelOutput* m, const Json::Value& config, const Json::Value& capeConfig);
115 void removeMember(BBShiftPanelOutput* m);
116
117 // One frame: every member scatters into the shared intermediate, then the
118 // last one to arrive triggers the single bit-plane pass, and the first
119 // SendData of the frame hands the result to the pump.
120 void prepMember(BBShiftPanelOutput* m, unsigned char* channelData);
121 void publishFrame();
122
123 void startingOutput();
124 void stoppingOutput();
125 void closeMember();
126
127 void dumpConfig();
128
129 bool isPWMPanel() const;
130 uint32_t numOutputSlots() const { return m_numOutputSlots; }
131 uint32_t numOutputRows() const { return numRows; }
132 uint32_t outputRowLen() const { return rowLen; }
133 int longestChain() const { return m_longestChain; }
134 ✗ int numOutputs() const { return m_numOutputs; }
135 int colorDepth() const { return m_colorDepth; }
136 int brightness() const { return m_brightness; }
137 uint8_t outputPinFor(int o) const { return outputPin[o]; }
138 uint8_t outputBankFor(int o) const { return outputBank[o]; }
139 uint16_t* intermediate() const { return currentChannelData; }
140 ✗ uint32_t intermediateSize() const { return rowLen * m_numOutputSlots * 6 * numRows; }
141
142 // the shared worker pool; members use it for their scatter pass
143 void processTasks(std::atomic<int>& counter);
144
145 private:
146 // the frame-shape settings every matrix on the cape has to agree on
147 struct PanelParams {
148 int panelWidth = 0;
149 int panelHeight = 0;
150 int panelScan = 0;
151 int colorDepth = 12;
152 int addressingMode = 0;
153 int panelType = 0;
154 int dataLayout = 0;
155 bool pwmDirectRow = false;
156 bool pwmShiftRow = false;
157 bool outputByRow = false;
158 bool outputBlankData = false;
159 bool sharedPRUSS = false;
160 std::string panelInterleave;
161 // An imported PWM register profile, replacing the chip's built-in
162 // table. regProfile[0..2] are the R/G/B word lists (equal length,
163 // empty when nothing was imported) and regProfileName identifies it
164 // in the log. See BBShiftPanel::buildRegisterProfile().
165 std::vector<uint16_t> regProfile[3];
166 std::string regProfileName;
167 // FM6373 family OE timing, in DCLK periods (the reference
168 // implementation's units) except the row period, which is in us
169 int oeClkLength = 0; // 0 = keep the historical 600ns pulse
170 int oeFirstClkLength = 0; // 0 = keep the historical 2us opener
171 int oeRowPeriodUs = 20;
172 // FM6373 family register upload grammar, in DCLK clocks
173 int regVsyncClocks = 3;
174 int regMidClocks = 11; // 0 skips the middle burst
175 int regPreClocks = 14;
176 int regSpacerClocks = 8; // 0 is valid: emit no gap at all
177 };
178 static PanelParams parsePanelParams(const Json::Value& config, const Json::Value& capeConfig);
179
180 bool parseCapeConfig(const Json::Value& capeConfig);
181 bool adoptPanelParams(const PanelParams& p);
182 bool checkCompatible(const PanelParams& p) const;
183 bool rebuild();
184 void teardownHardware();
185 void teardown();
186 void computeGeometry();
187
188 void PrepDataShift();
189 bool PrepDataPWM();
190 bool waitForPWMCommandTaken();
191
192 void StopPRU(bool wait = true);
193 int StartPRU();
194
195 void buildStrideSchedule();
196 void sendPanelInitPackets();
197 uint32_t computeMaxBrightnessCycles();
198 void setupBrightnessValues();
199 bool setupPWMRegisters();
200 void setupGCLKConfig();
201 void writeFM6373SeqWord(int idx);
202 void writeDP3364SeqWord(int idx);
203
204 std::vector<BBShiftPanelOutput*> m_members;
205 // members that have arrived in this frame's PrepData; the shared bit-plane
206 // pass runs when the count reaches the member count
207 int m_preppedThisFrame = 0;
208 // set by the bit-plane pass, cleared by the frame publish, so exactly one
209 // member's SendData hands the frame over
210 bool m_framePrepped = false;
211 // balanced Starting/StoppingOutput calls across the members
212 int m_activeMembers = 0;
213 int m_openMembers = 0;
214
215 BBBPru* pru = nullptr;
216 BBBPru* pwmPru = nullptr;
217 BBShiftPanelData* pruData = nullptr;
218
219 int m_panelWidth = 0;
220 int m_panelHeight = 0;
221 int m_panelScan = 0;
222 std::string m_panelInterleave = "";
223
224 int m_addressingMode = 0;
225 // 0 = standard, 1 = FM6126A, 2 = FM6127 - panels that need their config
226 // registers clocked out before they display (see sendPanelInitPackets)
227 int m_panelType = 0;
228 // How the framebuffer is routed onto the two RGB lanes. 0 is the normal
229 // HUB75 arrangement: the lanes carry the top and bottom halves of the
230 // panel and each addressed row lights two physical rows. A full height
231 // panel (scan == height) addresses every row on its own instead, so its
232 // lanes carry the LEFT and RIGHT halves of one row - 1 and 2 select which
233 // way round. See buildScatterMap.
234 int m_dataLayout = 0;
235 // PWM panels: drive the row lines with a direct binary row number
236 // instead of the DP32020A style row shift register
237 bool m_pwmDirectRow = false;
238 // FM6373 family only: the opposite opt-in. That family shipped with the
239 // direct binary row number as its only transport, so it stays the default
240 // there and the shift register has to be asked for explicitly - see
241 // sendPWMConfig().
242 bool m_pwmShiftRow = false;
243 // FM6373: next config sequence word for the per-frame rotating refresh
244 int m_pwmSeqIdx = 0;
245 // An imported register profile replacing the chip's built-in table. The
246 // word lists are owned here because m_profSeq points into them, so the
247 // two must not be copied apart. activePWMSeq() hands out &m_profSeq once
248 // m_haveProfile is set, and the built-in table otherwise.
249 std::vector<uint16_t> m_profWords[3];
250 PWMChipSeq m_profSeq{};
251 bool m_haveProfile = false;
252 std::string m_profName;
253 int m_oeClkLength = 0;
254 int m_oeFirstClkLength = 0;
255 int m_oeRowPeriodUs = 20;
256 int m_regVsyncClocks = 3;
257 int m_regMidClocks = 11;
258 int m_regPreClocks = 14;
259 int m_regSpacerClocks = 8;
260 void buildRegisterProfile(const PanelParams& p);
261 const PWMChipSeq* activePWMSeq() const;
262 // the pins this cape muxed to the PRU (only these are released on
263 // Close - a combo cape may leave pins for another driver to own)
264 std::vector<std::string> m_configuredPins;
265 std::string m_oePin = "P1-36";
266 // sharing the PRUSS with a string driver on the other PRU: single PRU,
267 // SPLIT1 ring, and never clear the shared RAM or the other PRU's memory
268 bool m_sharedPRUSS = false;
269 int m_longestChain = 0;
270 // the OE on-time is a property of the cape, not of one matrix, so the
271 // hardware brightness is the highest any member asked for; a member that
272 // wanted less scales its own gamma table down to reach it (see
273 // BBShiftPanelOutput::setupGamma)
274 int m_brightness = 10;
275 int m_colorDepth = 12;
276
277 bool m_outputByRow = false;
278 bool m_outputBlankData = false;
279
280 int m_numOutputs = 8; // number of configured outputs (from cape config)
281 int m_numOutputSlots = 8; // 8 or 16 - determines data width per pixel (6 or 12 bytes)
282 uint8_t outputPin[16] = { 0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7 }; // PRU pin (0-7) per output
283 uint8_t outputBank[16] = { 0 }; // 0=first bank, 1=second bank per output
284
285 // the shared 16 bit intermediate every member scatters into
286 uint16_t* currentChannelData = nullptr;
287
288 // Four output buffers allows for double buffering and extras for the PRU to read from while the next frame is being built
289 // With 4MB of memory reserved for transfer to PRU, 12 P5 panels per output with 12bit color depth (total 3.5MB)
290 constexpr static int NUM_OUTPUT_BUFFERS = 4;
291 std::array<uint8_t*, NUM_OUTPUT_BUFFERS> outputBuffers = { nullptr, nullptr, nullptr, nullptr };
292 uint8_t currOutputBuffer = 0;
293 // scan rows in the frame: the scan for split panels, the panel height
294 // for full height ones. PrepDataShift's per-row table is this big.
295 static constexpr uint32_t MAX_SCAN_ROWS = 64;
296 uint32_t numRows = 0;
297 uint32_t rowLen = 0;
298 uint32_t maxRowLen = 0;
299
300 // Schedule of strides within one pass of all rows. Bits whose on-time is
301 // a multiple of the stride shift-out time are split into multiple shorter
302 // pulses spread across the frame which multiplies the perceived refresh
303 // rate without changing the total light output (same total on-time).
304 struct StrideSlot {
305 uint8_t bit; // color depth bit this slot displays
306 bool primary; // first slot for this bit; the pixel mapping writes here
307 uint32_t onTime; // PRU cycles the display is on for this slot
308 };
309 std::vector<StrideSlot> m_strideSchedule;
310 // per row: (dstOffset, srcOffset) frame buffer copies for the duplicated
311 // strides of split bits
312 std::vector<std::vector<std::pair<uint32_t, uint32_t>>> m_dupCopies;
313
314 // Real-time thread that streams the current frame buffer into a ring in
315 // the 32KB PRU shared memory. The PRU cannot read DDR fast enough to
316 // keep 16 outputs fed (~1.1-1.8us per 64 byte read) but reads shared
317 // memory in 18 predictable cycles. See runPumpThread() and
318 // pru/SMEMRing.hp for the layout.
319 std::thread m_pumpThread;
320 std::atomic<bool> m_pumpRunning{ false };
321 // Set while the channel output thread is idle. Shift panels only hold an
322 // image for as long as the pump keeps re-shifting it, so a panel that is
323 // not being output to is simply dark - there is nothing to refresh and no
324 // reason to spend a core streaming a blank frame. The pump acts on this
325 // only at a frame boundary; see runPumpThread.
326 std::atomic<bool> m_pumpPaused{ false };
327 // owned by the pump thread, read by StoppingOutput to wait for the park
328 std::atomic<bool> m_pumpParked{ false };
329 // set once both cores have been halted, so the park is not attempted or
330 // undone twice
331 bool m_prusPaused = false;
332 void ParkPRUs();
333 void UnparkPRUs();
334 std::atomic<uint8_t*> m_frontBuffer{ nullptr };
335 // PWM panels send one frame per DATA command; the pump streams once per
336 // sequence bump so the byte flow stays in step with the commands
337 std::atomic<uint32_t> m_pumpSeq{ 0 };
338 uint32_t m_pumpedSeq = 0;
339 // A self-PWM panel refreshes from its own engine while its row scan free
340 // runs, so a gap in the shift out shows up as the image drifting
341 // vertically rather than as a glitch. The frame is therefore not handed
342 // to the PRU until the pump has the ring as full as it will go, and the
343 // pump is woken directly rather than found by polling.
344 std::mutex m_pumpMutex;
345 std::condition_variable m_pumpCV;
346 std::atomic<uint32_t> m_pumpPrimed{ 0 };
347 uint32_t m_frameBytes = 0;
348 BBBPruSMEMRing m_ring;
349 bool m_heapBuffers = false;
350 void runPumpThread();
351
352 bool singlePRU = false;
353 std::string m_refreshWarning;
354 // set while a PWM PRU has stopped taking commands (see
355 // waitForPWMCommandTaken); the warning comes down when it answers again
356 // or the hardware is torn down
357 bool m_pwmPruStalled = false;
358 std::string m_pwmStallWarning;
359
360 std::queue<std::function<void()>> bgTasks;
361 volatile bool bgThreadsRunning = false;
362 std::mutex bgTaskMutex;
363
364 // Serializes the output thread's per-frame calls (prepMember,
365 // publishFrame, starting/stoppingOutput) against membership changes. A
366 // config reload Init()s each replacement matrix outside outputThreadLock,
367 // after the earlier ones are already live, and adding a member rebuilds
368 // the shared PRU, frame buffers and every member's scatter map.
369 std::mutex m_memberLock;
370 std::condition_variable bgTaskCondVar;
371 std::atomic<int> bgThreadCount;
372 void runBackgroundTasks();
373 void startBackgroundThreads();
374 void stopBackgroundThreads();
375
376 friend class BBShiftPanelOutput;
377 };
378
379 class BBShiftPanelOutput : public ChannelOutput {
380 public:
381 BBShiftPanelOutput(unsigned int startChannel, unsigned int channelCount);
382 virtual ~BBShiftPanelOutput();
383
384 ✗ virtual std::string GetOutputType() const {
385 ✗ return "BB64 Panels";
386 }
387
388 virtual int Init(Json::Value config) override;
389 virtual int Close(void) override;
390
391 virtual int SendData(unsigned char* channelData) override;
392 virtual void PrepData(unsigned char* channelData) override;
393 virtual void DumpConfig(void) override;
394
395 virtual void GetRequiredChannelRanges(const std::function<void(int, int)>& addRange) override;
396
397 virtual void OverlayTestData(unsigned char* channelData, int cycleNum, float percentOfCycle, int testType, const Json::Value& config) override;
398 ✗ virtual bool SupportsTesting() const { return true; }
399
400 virtual void StartingOutput() override;
401 virtual void StoppingOutput() override;
402
403 // called by the manager
404 ✗ int longestChain() const { return m_longestChain; }
405 const PanelMatrix* panelMatrix() const { return m_panelMatrix; }
406 bool usesOutput(int o) const;
407 ✗ int configuredBrightness() const { return m_brightness; }
408 // (re)builds this member's map into the shared frame; the manager calls
409 // this whenever the shared geometry changes
410 bool buildScatterMap();
411 void setupGamma();
412 void scatterFrame(unsigned char* channelData);
413
414 private:
415 void releaseToManager();
416
417 Matrix* m_matrix = nullptr;
418 PanelMatrix* m_panelMatrix = nullptr;
419
420 int m_longestChain = 0;
421 int m_invertedData = 0;
422 int m_brightness = 10;
423 float m_gamma = 2.2;
424
425 FPPColorOrder m_colorOrder;
426
427 int m_panels = 0;
428 int m_width = 0;
429 int m_height = 0;
430
431 uint16_t gammaCurve[256];
432
433 // The gamma scatter map sorted by destination offset so the prep loop
434 // writes sequentially (streaming writes with random byte reads are much
435 // faster than random writes at panel counts where the data far exceeds
436 // the cache). Unmapped channels are excluded entirely.
437 std::vector<uint32_t> m_scatterOffsets;
438 std::vector<uint32_t> m_scatterSrc;
439
440 std::string m_autoCreatedModelName;
441
442 // set once the manager has accepted this matrix; an output whose Init
443 // failed must never reach the manager's per-frame accounting
444 bool m_registered = false;
445 // the manager releases the cape's pins when its last open member closes,
446 // so each member has to hand its share back exactly once - Close() may be
447 // called without a destructor following, or the object may be destroyed
448 // without Close() ever having run
449 bool m_closed = false;
450
451 // guards against the output being torn down (config reload) while the
452 // channel output thread is still inside PrepData/SendData
453 std::atomic<bool> m_stopping{ false };
454 std::atomic<int> m_inFlight{ 0 };
455
456 friend class BBShiftPanelManager;
457 };
458