GCC Code Coverage Report


Directory: ./
File: channeloutput/ILI9488.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 0 182 0.0%
Functions: 0 17 0.0%
Branches: 0 46 0.0%

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 #include "fpp-pch.h"
14
15 #include "fpp-json.h"
16
17 #include <sys/mman.h>
18 #include <fcntl.h>
19 #include <unistd.h>
20
21 #include "../log.h"
22
23 #include "ILI9488.h"
24
25 #define PAGE_SIZE (4 * 1024)
26 #define BLOCK_SIZE (4 * 1024)
27
28 // GPIO setup macros. Always use INP_GPIO(x) before using OUT_GPIO(x) or SET_GPIO_ALT(x,y)
29 #define INP_GPIO(g) *(m_gpio + ((g) / 10)) = *(m_gpio + ((g) / 10)) & ~(7 << (((g) % 10) * 3))
30 #define OUT_GPIO(g) *(m_gpio + ((g) / 10)) = *(m_gpio + ((g) / 10)) | (1 << (((g) % 10) * 3))
31 #define SET_GPIO_ALT(g, a) *(m_gpio + (((g) / 10))) = *(m_gpio + (((g) / 10))) | (((a) <= 3 ? (a) + 4 : (a) == 4 ? 3 \
32 : 2) \
33 << (((g) % 10) * 3))
34
35 #define GPIO_SET *(m_gpio + 7) // sets bits which are 1 ignores bits which are 0
36 #define GPIO_CLR *(m_gpio + 10) // clears bits which are 1 ignores bits which are 0
37
38 #define GET_GPIO(g) (*(m_gpio + 13) & (1 << g)) // 0 if LOW, (1<<g) if HIGH
39
40 #define GPIO_PULL *(m_gpio + 37) // Pull up/pull down
41 #define GPIO_PULLCLK0 *(m_gpio + 38) // Pull up/pull down clock
42
43 #define ILI_PIN_CSX 4
44 #define ILI_PIN_WRX 17
45 #define ILI_PIN_DCX 18
46 #define ILI_PIN_D1 22
47 #define ILI_PIN_D2 23
48 #define ILI_PIN_D3 24
49 #define ILI_PIN_D4 10
50 #define ILI_PIN_D5 25
51 #define ILI_PIN_D6 9
52 #define ILI_PIN_D7 11
53 #define ILI_PIN_D8 8
54 #define ILI_PIN_RST 7
55
56 const unsigned ILI_dataBits =
57 1 << ILI_PIN_D1 |
58 1 << ILI_PIN_D2 |
59 1 << ILI_PIN_D3 |
60 1 << ILI_PIN_D4 |
61 1 << ILI_PIN_D5 |
62 1 << ILI_PIN_D6 |
63 1 << ILI_PIN_D7 |
64 1 << ILI_PIN_D8;
65
66 const int ILI_allPins[12] = { ILI_PIN_CSX, ILI_PIN_WRX, ILI_PIN_DCX, ILI_PIN_D1, ILI_PIN_D2, ILI_PIN_D3, ILI_PIN_D4, ILI_PIN_D5, ILI_PIN_D6, ILI_PIN_D7, ILI_PIN_D8, ILI_PIN_RST };
67 const int ILI_dataPins[8] = { ILI_PIN_D1, ILI_PIN_D2, ILI_PIN_D3, ILI_PIN_D4, ILI_PIN_D5, ILI_PIN_D6, ILI_PIN_D7, ILI_PIN_D8 };
68
69 #include "Plugin.h"
70 class ILI9488Plugin : public FPPPlugins::Plugin, public FPPPlugins::ChannelOutputPlugin {
71 public:
72 ✗ ILI9488Plugin() :
73 ✗ FPPPlugins::Plugin("ILI9488") {
74 ✗ }
75 ✗ virtual ChannelOutput* createChannelOutput(unsigned int startChannel, unsigned int channelCount) override {
76 ✗ return new ILI9488Output(startChannel, channelCount);
77 }
78 };
79
80 extern "C" {
81 ✗ FPPPlugins::Plugin* createPlugin() {
82 ✗ return new ILI9488Plugin();
83 }
84 }
85
86 /////////////////////////////////////////////////////////////////////////////
87
88 /*
89 *
90 */
91 ✗ ILI9488Output::ILI9488Output(unsigned int startChannel, unsigned int channelCount) :
92 ThreadedChannelOutput(startChannel, channelCount),
93 ✗ m_initialized(0),
94 ✗ m_rows(480),
95 ✗ m_cols(320),
96 ✗ m_pixels(0),
97 ✗ m_bpp(24),
98 ✗ m_gpio_map(NULL),
99 ✗ m_gpio(NULL),
100 ✗ m_clearWRXDataBits(0),
101 ✗ m_bitDCX(0),
102 ✗ m_bitWRX(0) {
103 ✗ LogDebug(VB_CHANNELOUT, "ILI9488Output::ILI9488Output(%u, %u)\n",
104 startChannel, channelCount);
105
106 ✗ m_pixels = m_rows * m_cols;
107 ✗ m_useDoubleBuffer = 1;
108 ✗ }
109
110 /*
111 *
112 */
113 ✗ ILI9488Output::~ILI9488Output() {
114 ✗ LogDebug(VB_CHANNELOUT, "ILI9488Output::~ILI9488Output()\n");
115 ✗ }
116
117 /////////////////////////////////////////////////////////////////////////////
118
119 /*
120 *
121 */
122 ✗ int ILI9488Output::Init(Json::Value config) {
123 ✗ LogDebug(VB_CHANNELOUT, "ILI9488Output::Init(JSON)\n");
124
125 ✗ if (config.isMember("bpp"))
126 ✗ m_bpp = config["bpp"].asInt();
127
128 // RawSendData() sends 3 channels a pixel for the whole panel
129 ✗ if (m_channelCount < (unsigned int)m_pixels * 3) {
130 ✗ LogErr(VB_CHANNELOUT, "ILI9488: the panel needs %d channels, the output has %u\n",
131 m_pixels * 3, m_channelCount);
132 ✗ return 0;
133 }
134
135 ✗ if (!ILI9488_Init()) {
136 ✗ return 0;
137 }
138
139 ✗ return ThreadedChannelOutput::Init(config);
140 }
141
142 /*
143 *
144 */
145 ✗ int ILI9488Output::Close(void) {
146 ✗ LogDebug(VB_CHANNELOUT, "ILI9488Output::Close()\n");
147
148 ✗ ILI9488_Cleanup();
149
150 ✗ return ThreadedChannelOutput::Close();
151 }
152
153 /*
154 *
155 */
156 ✗ int ILI9488Output::RawSendData(unsigned char* channelData) {
157 ✗ LogExcess(VB_CHANNELOUT, "ILI9488Output::RawSendData(%p)\n", channelData);
158
159 ✗ if (!m_initialized)
160 ✗ return 0;
161
162 ✗ unsigned char* c = channelData;
163
164 ✗ ILI9488_Command(0x2c); // Write memory start
165
166 ✗ GPIO_SET = m_bitDCX; // Set the 'data mode' bit
167
168 unsigned int rgb565;
169
170 ✗ for (int p = 0; p < m_pixels; p++) {
171 ✗ if (m_bpp == 16) {
172 // byte by byte: an unsigned long load read 8 bytes at each
173 // 3 byte pixel, past the end of the data at the last ones
174 ✗ rgb565 = ((c[0] << 8) & 0b1111100000000000) |
175 ✗ ((c[1] << 3) & 0b0000011111100000) |
176 ✗ (c[2] >> 3);
177
178 ✗ ILI9488_SendByte(rgb565 >> 8);
179 ✗ ILI9488_SendByte(rgb565);
180
181 ✗ c += 3;
182 } else {
183 ✗ ILI9488_SendByte(*c++);
184 ✗ ILI9488_SendByte(*c++);
185 ✗ ILI9488_SendByte(*c++);
186 }
187 }
188
189 ✗ ILI9488_Command(0x00); // Null command, ends write
190
191 ✗ return m_channelCount;
192 }
193
194 /*
195 *
196 */
197 ✗ void ILI9488Output::DumpConfig(void) {
198 ✗ LogDebug(VB_CHANNELOUT, "ILI9488Output::DumpConfig()\n");
199 ✗ LogDebug(VB_CHANNELOUT, " Columns: %d\n", m_cols);
200 ✗ LogDebug(VB_CHANNELOUT, " Rows : %d\n", m_rows);
201 ✗ LogDebug(VB_CHANNELOUT, " Pixels : %d\n", m_pixels);
202
203 ✗ ThreadedChannelOutput::DumpConfig();
204 ✗ }
205
206 /*
207 *
208 */
209 ✗ bool ILI9488Output::ILI9488_Init(void) {
210 ✗ LogDebug(VB_CHANNELOUT, "ILI9488Output::ILI9488_Init()\n");
211
212 ✗ int mem_fd = 0;
213
214 ✗ if ((mem_fd = open("/dev/gpiomem", O_RDWR | O_SYNC)) < 0) {
215 // not exit(): fppd outlives an output that cannot start
216 ✗ LogErr(VB_CHANNELOUT, "Can't open /dev/gpiomem\n");
217 ✗ return false;
218 }
219 ✗ LogDebug(VB_CHANNELOUT, "Opened /dev/gpiomem, mem_fd = %d\n", mem_fd);
220
221 ✗ off_t gpio_base = 0x200000;
222
223 ✗ m_gpio_map = mmap(
224 NULL, //Any adddress in our space will do
225 BLOCK_SIZE, //Map length
226 PROT_READ | PROT_WRITE, // Enable reading & writting to mapped memory
227 MAP_SHARED, //Shared with other processes
228 mem_fd, //File to map
229 gpio_base //Offset to GPIO peripheral
230 );
231
232 ✗ close(mem_fd); //No need to keep mem_fd open after mmap
233
234 ✗ if (m_gpio_map == MAP_FAILED) {
235 ✗ LogErr(VB_CHANNELOUT, "mmap error %p\n", m_gpio_map); //errno also set!
236 ✗ m_gpio_map = NULL;
237 ✗ return false;
238 }
239
240 // Always use volatile pointer!
241 ✗ m_gpio = (volatile unsigned*)m_gpio_map;
242
243 ✗ LogDebug(VB_CHANNELOUT, "Initializing GPIO pins for output\n");
244 ✗ int i = 0;
245 ✗ for (i = 0; i < 12; i++) {
246 ✗ INP_GPIO(ILI_allPins[i]);
247 ✗ OUT_GPIO(ILI_allPins[i]);
248 }
249
250 ✗ LogDebug(VB_CHANNELOUT, "Initializing data lookup table\n");
251
252 // Initialize our lookup table
253 int bit;
254 ✗ for (i = 0; i < 256; i++) {
255 ✗ m_dataValues[i] = 0;
256 ✗ for (bit = 0; bit < 8; bit++) {
257 ✗ m_dataValues[i] |= (1 & (i >> bit)) << ILI_dataPins[bit];
258 }
259 }
260
261 ✗ m_clearWRXDataBits = (1 << ILI_PIN_WRX) | ILI_dataBits;
262 ✗ m_bitWRX = 1 << ILI_PIN_WRX;
263 ✗ m_bitDCX = 1 << ILI_PIN_DCX;
264
265 ✗ GPIO_CLR = 1 << ILI_PIN_CSX;
266
267 ✗ ILI9488_Command(0x11); // Exit sleep
268 ✗ usleep(100000);
269
270 // ILI9488_Command(0x01); // Software Reset
271 // usleep(150000);
272
273 // ILI9488_Command(0x13); // Normal Display Mode
274
275 ✗ ILI9488_Command(0x36); // Memory Access Control
276 ✗ ILI9488_Data(0b10001000); // RGB with top closest to Pi GPIO connector
277 //ILI9488_Data(0x48);
278 // 0x48 = 0b01001000
279 // 0 - Row Address Order (vertical flip of frame data)
280 // 1 - Column Address Order (horizontal flip of frame data)
281 // 0 - Row/Column exchange (1 = 480x320, 0 = 320x480
282 // 0 - Vertical Refresh Order
283 // 1 - RGB or BGR (0 = RGB, 1 = BGR)
284 // 0 - Horizontal Refresh Order
285 // 0 - no hflip
286 // 0 - no flip
287
288 ✗ ILI9488_Command(0xE0); // Positive Gamma Control
289 ✗ ILI9488_Data(0x00);
290 ✗ ILI9488_Data(0x04);
291 ✗ ILI9488_Data(0x0E);
292 ✗ ILI9488_Data(0x08);
293 ✗ ILI9488_Data(0x17);
294 ✗ ILI9488_Data(0x0A);
295 ✗ ILI9488_Data(0x40);
296 ✗ ILI9488_Data(0x79);
297 ✗ ILI9488_Data(0x4D);
298 ✗ ILI9488_Data(0x07);
299 ✗ ILI9488_Data(0x0E);
300 ✗ ILI9488_Data(0x0A);
301 ✗ ILI9488_Data(0x1A);
302 ✗ ILI9488_Data(0x1D);
303 ✗ ILI9488_Data(0x0F);
304
305 ✗ ILI9488_Command(0xE1); // Negative Gamma Control
306 ✗ ILI9488_Data(0x00);
307 ✗ ILI9488_Data(0x1B);
308 ✗ ILI9488_Data(0x1F);
309 ✗ ILI9488_Data(0x02);
310 ✗ ILI9488_Data(0x10);
311 ✗ ILI9488_Data(0x05);
312 ✗ ILI9488_Data(0x32);
313 ✗ ILI9488_Data(0x34);
314 ✗ ILI9488_Data(0x43);
315 ✗ ILI9488_Data(0x02);
316 ✗ ILI9488_Data(0x0A);
317 ✗ ILI9488_Data(0x09);
318 ✗ ILI9488_Data(0x33);
319 ✗ ILI9488_Data(0x37);
320 ✗ ILI9488_Data(0x0F);
321
322 ✗ ILI9488_Command(0xB1); // Frame Rate
323 ✗ ILI9488_Data(0x00); // as low as it can go, ~28hz
324
325 ✗ ILI9488_Command(0xB0); // Interface mode control
326 ✗ ILI9488_Data(0x00);
327
328 ✗ ILI9488_Command(0x3A); // Set pixel format
329
330 ✗ if (m_bpp == 16)
331 ✗ ILI9488_Data(0x55); // 16bpp RGB565
332 else
333 ✗ ILI9488_Data(0x66); // 18bpp (24bpp w/ lower 2bpp bits ignored)
334
335 ✗ SetRegion(0, 0, m_cols - 1, m_rows - 1);
336
337 ✗ ILI9488_Command(0x29); //display on
338
339 // Not sure if we need to do this twice or not, but sample python did
340 ✗ SetRegion(0, 0, m_cols - 1, m_rows - 1);
341
342 ✗ m_initialized = 1;
343
344 ✗ LogDebug(VB_CHANNELOUT, "Initialization complete\n");
345 ✗ return true;
346 }
347
348 /*
349 *
350 */
351 ✗ inline void ILI9488Output::ILI9488_SendByte(unsigned char byte) {
352 ✗ GPIO_CLR = m_clearWRXDataBits;
353
354 ✗ GPIO_SET = m_dataValues[byte];
355
356 ✗ GPIO_SET = m_bitWRX;
357 ✗ }
358
359 /*
360 *
361 */
362 ✗ void ILI9488Output::ILI9488_Command(unsigned char cmd) {
363 ✗ LogExcess(VB_CHANNELOUT, "ILI9488_Command(0x%02x)\n", cmd);
364
365 ✗ GPIO_CLR = m_bitDCX;
366 ✗ ILI9488_SendByte(cmd);
367 ✗ }
368
369 /*
370 *
371 */
372 ✗ void ILI9488Output::ILI9488_Data(unsigned char data) {
373 ✗ LogExcess(VB_CHANNELOUT, "ILI9488_Data(0x%02x)\n", data);
374
375 ✗ GPIO_SET = m_bitDCX;
376 ✗ ILI9488_SendByte(data);
377 ✗ }
378
379 /*
380 *
381 */
382 ✗ void ILI9488Output::ILI9488_Cleanup(void) {
383 ✗ LogDebug(VB_CHANNELOUT, "ILI9488_Cleanup()\n");
384
385 ✗ GPIO_CLR = 1 << ILI_PIN_CSX;
386 ✗ }
387
388 /*
389 *
390 */
391 ✗ inline void ILI9488Output::SetColumnRange(unsigned int x1, unsigned int x2) {
392 // ILI9488_Command( 0x2B ); // Use 'row' setting here because we rotate display
393 ✗ ILI9488_Command(0x2A);
394 ✗ ILI9488_Data(x1 >> 8);
395 ✗ ILI9488_Data(x1);
396 ✗ ILI9488_Data(x2 >> 8);
397 ✗ ILI9488_Data(x2);
398 ✗ }
399
400 /*
401 *
402 */
403 ✗ inline void ILI9488Output::SetRowRange(unsigned int y1, unsigned int y2) {
404 // ILI9488_Command( 0x2A ); // Use 'column' setting here because we rotate display
405 ✗ ILI9488_Command(0x2B);
406 ✗ ILI9488_Data(y1 >> 8);
407 ✗ ILI9488_Data(y1);
408 ✗ ILI9488_Data(y2 >> 8);
409 ✗ ILI9488_Data(y2);
410 ✗ }
411
412 /*
413 *
414 */
415 ✗ inline void ILI9488Output::SetRegion(unsigned int x1, unsigned int y1, unsigned int x2, unsigned int y2) {
416 ✗ SetColumnRange(x1, x2);
417 ✗ SetRowRange(y1, y2);
418 ✗ }
419