GCC Code Coverage Report


Directory: ./
File: channeloutput/PixelString.h
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 10 10 100.0%
Functions: 5 5 100.0%
Branches: 4 6 66.7%

Line Branch Exec Source
1 #pragma once
2 /*
3 * This file is part of the Falcon Player (FPP) and is Copyright (C)
4 * 2013-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 LGPL v2.1 as described in the
11 * included LICENSE.LGPL file.
12 */
13
14 #include <cmath>
15 #include "fpp-json.h"
16 #include <list>
17 #include <stdint.h>
18 #include <string>
19 #include <vector>
20
21 #include "ColorOrder.h"
22
23 // Brightness/gamma lookup tables are a pure function of (brightness, gamma) -
24 // see ReadVirtualString - and it is very common for many ports to carry the
25 // same settings, a whole megatree being the obvious case. Handing those a
26 // single shared table instead of a private copy each keeps the tables the
27 // frame interleave is walking small enough to stay resident, and drops the
28 // memory from one table per virtual string to one per distinct setting.
29 //
30 // Sharing is also why inversion cannot flip a table in place any more: the
31 // inverted form is just a different cached table.
32 //
33 // Tables live for the life of the process. They are only ever built during
34 // output Init and are read-only afterwards, so the lock is uncontended.
35 class BrightnessLUTCache {
36 public:
37 // brightness is 0-100; inverted returns the complemented table used by a
38 // line that idles high. Never returns null.
39 static const uint8_t* get(int brightness, float gamma, bool inverted);
40
41 // The 16 bit form for parts like UCS8903. Sequence data is always 8 bit
42 // per channel, so this is where the extra range comes from: the same
43 // brightness/gamma curve evaluated to 65535 instead of 255. That is the
44 // whole point of a 16 bit part - at gamma 2.2 the low 8 bit inputs all
45 // round to the same output, and expanding at the wider width keeps them
46 // distinct. Simply duplicating the byte would buy nothing.
47 static const uint16_t* getWide(int brightness, float gamma, bool inverted);
48
49 // distinct tables built so far - diagnostics and tests
50 static size_t tableCount();
51 };
52
53 class VirtualString {
54 public:
55 VirtualString();
56 VirtualString(int receiverType, int receiverNum);
57 1156 ~VirtualString() {};
58
59 int channelsPerNode() const;
60
61 bool isSmartReceiver() const;
62 bool isFalconV5SmartReceiver() const;
63
64 int startChannel;
65 int pixelCount;
66 int groupCount;
67 int reverse;
68 FPPColorOrder colorOrder;
69 int startNulls;
70 int endNulls;
71 int zigZag;
72 int brightness;
73 float gamma;
74 // owned by BrightnessLUTCache and shared with every virtual string that
75 // has the same brightness/gamma/inversion - do not write through it
76 const uint8_t* brightnessMap;
77 // the 16 bit form of the same curve; only set on a 16 bit port
78 const uint16_t* brightnessMap16 = nullptr;
79
80 int8_t receiverNum;
81 uint8_t leadInCount;
82 uint8_t toggleCount;
83 uint8_t leadOutCount;
84 std::string description;
85
86 int* chMap;
87 int chMapCount;
88 };
89
90 class GPIOCommand {
91 public:
92 615 GPIOCommand(int p, int offset, int tp = 0, int bo = 0) :
93 615 port(p),
94 615 channelOffset(offset),
95 615 type(tp),
96 615 bitOffset(bo) {}
97 2655 ~GPIOCommand() {}
98
99 int port;
100 int channelOffset;
101 int type; // 0 for off, 1 for on
102 int bitOffset;
103 };
104
105 class PixelString {
106 public:
107 PixelString(bool supportsSmartReceivers = false);
108 ~PixelString();
109
110 int Init(Json::Value config, Json::Value* pinConfig = nullptr);
111 void DumpConfig(void);
112 void invertOutput();
113
114 // Pixel protocols whose line idles high and pulses low (the TM18xx
115 // family). The bit cell is otherwise identical to ws2811, so a driver
116 // that can flip the idle level per port drives them alongside normal
117 // strings at no cost - see m_isInverted.
118 static bool isInvertedProtocol(const std::string& protocol);
119
120 // TM1814's C1/C2 words: four 6 bit constant current levels in the chip's
121 // physical W,R,G,B pin order, then the bitwise complement of all four
122 // (the chip refuses to decode the frame without it). milliAmps is per
123 // channel and clamps to the 6.5-38mA the part supports.
124 static std::vector<uint8_t> buildTM1814Preamble(float milliAmps);
125 static bool protocolNeedsTM1814Preamble(const std::string& protocol);
126
127 // Bytes clocked per colour channel: 1 for the usual 8 bit parts, 2 for the
128 // 16 bit ones. The sequence still supplies 8 bits either way - see
129 // BrightnessLUTCache::getWide - so this only changes the wire length.
130 static int protocolBytesPerChannel(const std::string& protocol);
131
132 // The bit cell a protocol wants, in ns. t0/t1 are how long the line is
133 // driven for a zero and a one; period is the whole cell. These are the
134 // instants every port on a PRU shares, so a driver can only honour one set
135 // at a time - see BBShiftString, which derives the controller's timing
136 // from the ports in use and refuses to mix.
137 struct Timing {
138 int t0Ns;
139 int t1Ns;
140 int periodNs;
141 49 bool operator==(const Timing& o) const {
142
4/6
✓ Branch 0 taken 46 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 46 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 46 times.
✗ Branch 5 not taken.
49 return t0Ns == o.t0Ns && t1Ns == o.t1Ns && periodNs == o.periodNs;
143 }
144 };
145 static Timing protocolTiming(const std::string& protocol);
146
147 // Set the wire length from a count of colour channels. The licensed
148 // output clamps rebuild that count from the (reduced) virtual strings, so
149 // it is channels and excludes both the protocol preamble and any 16 bit
150 // widening - assigning it straight to m_outputBytes would quietly drop
151 // them, so route it through here.
152 void setPixelDataChannels(int channels);
153
154 // Drop a preamble the driver cannot honour and shorten the port back to
155 // plain pixel data, keeping m_outputBytes and the gpio commands in step.
156 // Without this the leading bytes would offset every pixel on the port.
157 void dropPreamble();
158
159 int m_portNumber;
160 int m_channelOffset;
161 // bytes clocked out on this port, including any protocol preamble
162 int m_outputBytes;
163 uint8_t* m_outputBuffer;
164
165 // Constant bytes the protocol requires ahead of the pixel data - TM1814's
166 // C1/C2 constant current words. Counted in m_outputBytes and parked
167 // permanently at the head of m_outputBuffer, so anything that fills the
168 // buffer just has to start at pixelDataStart() and the preamble comes
169 // along for free.
170 std::vector<uint8_t> m_preamble;
171 2949 uint8_t* pixelDataStart() const { return m_outputBuffer + m_preamble.size(); }
172
173 std::vector<VirtualString> m_virtualStrings;
174 std::vector<GPIOCommand> m_gpioCommands;
175
176 std::vector<int> m_outputMap;
177 const uint8_t** m_brightnessMaps;
178
179 enum class ReceiverType {
180 None = -1,
181 Standard = 0,
182 v1 = 1,
183 v2 = 2,
184 // Falcon differential receivers: FalconV5 is the full bidirectional
185 // protocol (config + queries + listen, needs cape listener support);
186 // FalconV4 only sends the config packet
187 FalconV5 = 3,
188 FalconV4 = 4
189 };
190 ReceiverType smartReceiverType = PixelString::ReceiverType::Standard;
191 bool m_isSmartReceiver = false;
192
193 // The line for this port idles high instead of low. Two independent
194 // things set this and they compose by xor:
195 //
196 // - the cape declaring "inverted" on the port, which compensates for a
197 // differential pair wired backwards (one batch of K16A-B boards had
198 // this on the second line of the first differential group). The far
199 // end sees a correct signal, so smart receivers are unaffected.
200 // - the port's protocol being one of the TM18xx family, which really
201 // does want an inverted line all the way to the pixels. A smart
202 // receiver in that path would see inverted data, so drivers refuse
203 // the combination.
204 //
205 // A TM18xx string on a backwards-wired port cancels out, which is what
206 // the hardware actually does.
207 bool m_isInverted = false;
208 // 1, or 2 for a 16 bit part. Set before the virtual strings are read so
209 // the wire length and the lookup tables agree.
210 int m_bytesPerChannel = 1;
211 // set when m_isInverted came from the protocol rather than the cape, so
212 // drivers can apply the smart receiver restriction to just that case
213 bool m_protocolInverted = false;
214
215 static void AutoCreateOverlayModels(const std::vector<PixelString*>& strings, std::list<std::string>& autoModelNames);
216
217 // returned buffer is owned by the PixelString and reused next frame
218 uint8_t* prepareOutput(uint8_t* channelData);
219
220 private:
221 void SetupMap(int vsOffset, const VirtualString& vs);
222 void FlipPixels(int offset1, int offset2, int chanCount);
223 void DumpMap(const char* msg);
224
225 int ReadVirtualString(Json::Value& vsc, VirtualString& vs) const;
226 void AddVirtualString(const VirtualString& vs);
227 void AddNullPixelString();
228
229 Json::Value m_pinConfig;
230 };
231