| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * This file is part of the Falcon Player (FPP) and is Copyright (C) | ||
| 3 | * 2013-2022 by the Falcon Player Developers. | ||
| 4 | * | ||
| 5 | * The Falcon Player (FPP) is free software, and is covered under | ||
| 6 | * multiple Open Source licenses. Please see the included 'LICENSES' | ||
| 7 | * file for descriptions of what files are covered by each license. | ||
| 8 | * | ||
| 9 | * This source file is covered under the GPL v2 as described in the | ||
| 10 | * included LICENSE.GPL file. | ||
| 11 | */ | ||
| 12 | |||
| 13 | /* | ||
| 14 | * This Channel Output is for SPI-attached MAX7219 chips connected to | ||
| 15 | * 8x8 LED panels. The chips/panels may be daisy-chained to provide | ||
| 16 | * wider displays. | ||
| 17 | * | ||
| 18 | * Sample channeloutputs.json config | ||
| 19 | * | ||
| 20 | * { | ||
| 21 | * "channelOutputs": [ | ||
| 22 | * { | ||
| 23 | * "type": "MAX7219Matrix", | ||
| 24 | * "enabled": 1, | ||
| 25 | * "startChannel": 1, | ||
| 26 | * "channelCount": 128, | ||
| 27 | * "panels": 2, | ||
| 28 | * "channelsPerPixel": 1 | ||
| 29 | * } | ||
| 30 | * ] | ||
| 31 | * } | ||
| 32 | * | ||
| 33 | */ | ||
| 34 | #include "fpp-pch.h" | ||
| 35 | |||
| 36 | #include "fpp-json.h" | ||
| 37 | |||
| 38 | #include <unistd.h> | ||
| 39 | |||
| 40 | #include "../log.h" | ||
| 41 | |||
| 42 | #include "MAX7219Matrix.h" | ||
| 43 | |||
| 44 | #define MAX7219_DECODE_MODE 0x09 | ||
| 45 | #define MAX7219_SHUTDOWN 0x0C | ||
| 46 | #define MAX7219_BRIGHTNESS 0x0A | ||
| 47 | #define MAX7219_SCAN_LIMIT 0x0B | ||
| 48 | #define MAX7219_TEST 0x0F | ||
| 49 | |||
| 50 | #include "Plugin.h" | ||
| 51 | class MAX7219MatrixPlugin : public FPPPlugins::Plugin, public FPPPlugins::ChannelOutputPlugin { | ||
| 52 | public: | ||
| 53 | 3 | MAX7219MatrixPlugin() : | |
| 54 |
2/4✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
9 | FPPPlugins::Plugin("MAX7219Matrix") { |
| 55 | 3 | } | |
| 56 | 3 | virtual ChannelOutput* createChannelOutput(unsigned int startChannel, unsigned int channelCount) override { | |
| 57 |
2/6✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
6 | return new MAX7219MatrixOutput(startChannel, channelCount); |
| 58 | } | ||
| 59 | }; | ||
| 60 | |||
| 61 | extern "C" { | ||
| 62 | 3 | FPPPlugins::Plugin* createPlugin() { | |
| 63 |
2/6✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
6 | return new MAX7219MatrixPlugin(); |
| 64 | } | ||
| 65 | } | ||
| 66 | |||
| 67 | /* | ||
| 68 | * | ||
| 69 | */ | ||
| 70 | 3 | MAX7219MatrixOutput::MAX7219MatrixOutput(unsigned int startChannel, unsigned int channelCount) : | |
| 71 | ChannelOutput(startChannel, channelCount), | ||
| 72 | 3 | m_panels(1), | |
| 73 | 3 | m_channelsPerPixel(1), | |
| 74 | 3 | m_pinCS(8), | |
| 75 | 3 | m_csPin(nullptr), | |
| 76 | 3 | m_spi(nullptr) { | |
| 77 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | LogDebug(VB_CHANNELOUT, "MAX7219MatrixOutput::MAX7219MatrixOutput(%u, %u)\n", |
| 78 | startChannel, channelCount); | ||
| 79 | |||
| 80 |
2/4✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
3 | m_csPin = PinCapabilities::getPinByGPIO(0, 8).ptr(); |
| 81 | 3 | } | |
| 82 | |||
| 83 | /* | ||
| 84 | * | ||
| 85 | */ | ||
| 86 | 12 | MAX7219MatrixOutput::~MAX7219MatrixOutput() { | |
| 87 | 6 | LogDebug(VB_CHANNELOUT, "MAX7219MatrixOutput::~MAX7219MatrixOutput()\n"); | |
| 88 | |||
| 89 | 6 | Close(); | |
| 90 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
6 | if (m_spi) { |
| 91 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
6 | delete m_spi; |
| 92 | } | ||
| 93 | 12 | } | |
| 94 | |||
| 95 | /* | ||
| 96 | * | ||
| 97 | */ | ||
| 98 | 3 | int MAX7219MatrixOutput::Init(Json::Value config) { | |
| 99 | 3 | LogDebug(VB_CHANNELOUT, "MAX7219MatrixOutput::Init(JSON)\n"); | |
| 100 | |||
| 101 |
2/6✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
3 | m_spi = new SPIUtils(0, 1000000); |
| 102 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
|
3 | if (!m_spi->isOk()) { |
| 103 | ✗ | LogErr(VB_CHANNELOUT, "SPIUtils setup failed\n"); | |
| 104 | ✗ | return 0; | |
| 105 | } | ||
| 106 | |||
| 107 | 3 | m_panels = config["panels"].asInt(); | |
| 108 | 3 | m_channelsPerPixel = config["channelsPerPixel"].asInt(); | |
| 109 | // SendData() builds each line in a 256 byte stack buffer, two bytes a | ||
| 110 | // panel, and reads 64 pixels a panel from the channel data: more than | ||
| 111 | // 128 panels overflowed the stack, and a short channel count over-read. | ||
| 112 |
4/8✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 2 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
|
3 | if (m_panels < 1 || m_panels > 128 || (m_channelsPerPixel != 1 && m_channelsPerPixel != 3)) { |
| 113 | 1 | LogErr(VB_CHANNELOUT, "MAX7219: invalid config, %d panels (1-128) with %d channels per pixel (1 or 3)\n", | |
| 114 | m_panels, m_channelsPerPixel); | ||
| 115 | 1 | return 0; | |
| 116 | } | ||
| 117 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
|
2 | if (m_channelCount < m_panels * 64 * m_channelsPerPixel) { |
| 118 | 1 | LogErr(VB_CHANNELOUT, "MAX7219: %d panels need %d channels, the output has %d\n", | |
| 119 | m_panels, m_panels * 64 * m_channelsPerPixel, m_channelCount); | ||
| 120 | 1 | return 0; | |
| 121 | } | ||
| 122 | |||
| 123 |
3/6✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
|
4 | m_csPin->configPin("gpio", true); |
| 124 | 1 | m_usedGPIOS.push_back(m_csPin->name); | |
| 125 | |||
| 126 | 1 | usleep(50000); | |
| 127 | |||
| 128 | 1 | WriteCommand(MAX7219_DECODE_MODE, 0x00); // Use LED Matrix, not BCD Mode | |
| 129 | 1 | WriteCommand(MAX7219_BRIGHTNESS, 0x00); // Lowest brightness | |
| 130 | 1 | WriteCommand(MAX7219_SCAN_LIMIT, 0x07); // Use all 8 columns | |
| 131 | 1 | WriteCommand(MAX7219_SHUTDOWN, 0x01); // (1 == On, 0 == Off) | |
| 132 | 1 | WriteCommand(MAX7219_TEST, 0x00); // Turn Off Test mode | |
| 133 | |||
| 134 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | return ChannelOutput::Init(config); |
| 135 | } | ||
| 136 | |||
| 137 | 2 | void MAX7219MatrixOutput::GetRequiredChannelRanges(const std::function<void(int, int)>& addRange) { | |
| 138 | 2 | addRange(m_startChannel, m_startChannel + m_channelCount - 1); | |
| 139 | 2 | } | |
| 140 | |||
| 141 | /* | ||
| 142 | * | ||
| 143 | */ | ||
| 144 | 4 | int MAX7219MatrixOutput::Close(void) { | |
| 145 | 4 | LogDebug(VB_CHANNELOUT, "MAX7219MatrixOutput::Close()\n"); | |
| 146 | |||
| 147 | 4 | return ChannelOutput::Close(); | |
| 148 | } | ||
| 149 | |||
| 150 | /* | ||
| 151 | * | ||
| 152 | */ | ||
| 153 | 5 | void MAX7219MatrixOutput::WriteCommand(uint8_t cmd, uint8_t value) { | |
| 154 | uint8_t c; | ||
| 155 | uint8_t v; | ||
| 156 | uint8_t data[256]; | ||
| 157 | 5 | int bytes = 0; | |
| 158 | |||
| 159 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 5 times.
|
10 | for (int p = 0; p < m_panels; p++) { |
| 160 | 5 | data[bytes++] = cmd; | |
| 161 | 5 | data[bytes++] = value; | |
| 162 | } | ||
| 163 | |||
| 164 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | m_csPin->setValue(0); |
| 165 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | m_spi->xfer(data, nullptr, bytes); |
| 166 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | m_csPin->setValue(1); |
| 167 | 5 | } | |
| 168 | |||
| 169 | /* | ||
| 170 | * | ||
| 171 | */ | ||
| 172 | 4 | int MAX7219MatrixOutput::SendData(unsigned char* channelData) { | |
| 173 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | LogExcess(VB_CHANNELOUT, "MAX7219MatrixOutput::SendData(%p)\n", |
| 174 | channelData); | ||
| 175 | |||
| 176 | uint8_t data[256]; | ||
| 177 | |||
| 178 | // Line by line, send data starting with the last panel first and | ||
| 179 | // left most pixel on a panel is MSB | ||
| 180 | 4 | int c = 0; | |
| 181 |
2/2✓ Branch 0 taken 32 times.
✓ Branch 1 taken 4 times.
|
36 | for (int i = 1; i < 9; i++) { |
| 182 | 32 | int bytes = 0; | |
| 183 | 32 | int offset = m_panels * 8 * m_channelsPerPixel; | |
| 184 | |||
| 185 |
2/2✓ Branch 0 taken 32 times.
✓ Branch 1 taken 32 times.
|
64 | for (int p = 0; p < m_panels; p++) { |
| 186 | 32 | data[bytes++] = i; | |
| 187 | |||
| 188 | 32 | uint8_t ch = 0; | |
| 189 |
2/2✓ Branch 0 taken 256 times.
✓ Branch 1 taken 32 times.
|
288 | for (int j = 0; j < 8; j++) { |
| 190 |
1/2✓ Branch 0 taken 256 times.
✗ Branch 1 not taken.
|
256 | if (m_channelsPerPixel == 1) { |
| 191 |
2/2✓ Branch 0 taken 128 times.
✓ Branch 1 taken 128 times.
|
256 | if (channelData[c + offset - 1]) |
| 192 | 128 | ch |= 0x01 << j; | |
| 193 | } else { // 3 channels per pixel | ||
| 194 | ✗ | if ((channelData[c + offset - 1]) || | |
| 195 | ✗ | (channelData[c + offset - 2]) || | |
| 196 | ✗ | (channelData[c + offset - 3])) | |
| 197 | ✗ | ch |= 0x01 << j; | |
| 198 | } | ||
| 199 | 256 | offset -= m_channelsPerPixel; | |
| 200 | } | ||
| 201 | 32 | data[bytes++] = ch; | |
| 202 | } | ||
| 203 | |||
| 204 |
1/2✓ Branch 0 taken 32 times.
✗ Branch 1 not taken.
|
32 | m_csPin->setValue(0); |
| 205 |
1/2✓ Branch 0 taken 32 times.
✗ Branch 1 not taken.
|
32 | m_spi->xfer(data, nullptr, bytes); |
| 206 |
1/2✓ Branch 0 taken 32 times.
✗ Branch 1 not taken.
|
32 | m_csPin->setValue(1); |
| 207 | |||
| 208 | 32 | c += m_panels * 8 * m_channelsPerPixel; | |
| 209 | } | ||
| 210 | |||
| 211 | 4 | return m_channelCount; | |
| 212 | } | ||
| 213 | |||
| 214 | /* | ||
| 215 | * | ||
| 216 | */ | ||
| 217 | 1 | void MAX7219MatrixOutput::DumpConfig(void) { | |
| 218 | 1 | LogDebug(VB_CHANNELOUT, "MAX7219MatrixOutput::DumpConfig()\n"); | |
| 219 | |||
| 220 | 1 | ChannelOutput::DumpConfig(); | |
| 221 | 1 | } | |
| 222 |