| 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 |