GCC Code Coverage Report


Directory: ./
File: oled/I2C1602_2004_DisplayDriver.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 0 212 0.0%
Functions: 0 25 0.0%
Branches: 0 248 0.0%

Line Branch Exec Source
1 #include "fpp-pch.h"
2 #include <cstring>
3
4 #include <fcntl.h>
5 #include <thread>
6 #include <unistd.h>
7
8 #include "DisplayDriver.h"
9 #include "util/GPIOUtils.h"
10
11 #if defined(PLATFORM_BBB) || defined(PLATFORM_BB64)
12 #include "util/BBBUtils.h"
13 #define PLAT_GPIO_CLASS BBBPinProvider
14 #elif defined(PLATFORM_PI)
15 #include "util/PiGPIOUtils.h"
16 #define PLAT_GPIO_CLASS PiGPIOPinProvider
17 #else
18 #include "util/TmpFileGPIO.h"
19 #define PLAT_GPIO_CLASS TmpFilePinProvider
20 #endif
21
22 static constexpr int LED_TYPE_0x20_1602 = 11;
23 static constexpr int LED_TYPE_0x3f_1602 = 12;
24 static constexpr int LED_TYPE_0x3f_2004 = 13;
25 static constexpr int LED_TYPE_0x27_PCF_1602 = 14;
26 static constexpr int LED_TYPE_0x27_PCF_2004 = 15;
27
28 static constexpr int RS_PIN = 0;
29 static constexpr int RW_PIN = 1;
30 static constexpr int E_PIN = 2;
31 static constexpr int D4_PIN = 3;
32 static constexpr int D5_PIN = 4;
33 static constexpr int D6_PIN = 5;
34 static constexpr int D7_PIN = 6;
35
36 static std::vector<unsigned int> lineOffsets0x3f = { 0, 1, 2, 4, 5, 6, 7 };
37 static std::vector<unsigned int> lineOffset0x3fBacklight = { 3 };
38 static std::vector<unsigned int> lineOffsets0x20 = { 15, 14, 13, 12, 11, 10, 9 };
39 static std::vector<unsigned int> lineOffset0x20Backlight = { 7, 6, 8 };
40
41 #if defined(PLATFORM_BBB)
42 static constexpr int DEFAULT_I2C_BUS = 2;
43 #elif defined(PLATFORM_PI)
44 static constexpr int DEFAULT_I2C_BUS = 1;
45 #else
46 static constexpr int DEFAULT_I2C_BUS = 0;
47 #endif
48
49 #if __has_include(<gpiod.hpp>)
50 #include <gpiod.hpp>
51 #endif
52
53 ✗ I2C1602_2004_DisplayDriver::I2C1602_2004_DisplayDriver(int lt) :
54 ✗ DisplayDriver(lt) {
55 ✗ }
56 ✗ I2C1602_2004_DisplayDriver::~I2C1602_2004_DisplayDriver() {
57 ✗ }
58 ✗ int I2C1602_2004_DisplayDriver::getWidth() {
59 ✗ if (ledType == LED_TYPE_0x20_1602 || ledType == LED_TYPE_0x3f_1602 || ledType == LED_TYPE_0x27_PCF_1602) {
60 ✗ return 16 * 6;
61 }
62 ✗ return 20 * 6;
63 }
64 ✗ int I2C1602_2004_DisplayDriver::getHeight() {
65 ✗ if (ledType == LED_TYPE_0x20_1602 || ledType == LED_TYPE_0x3f_1602 || ledType == LED_TYPE_0x27_PCF_1602) {
66 ✗ return 16;
67 }
68 ✗ return 32;
69 }
70
71 ✗ static std::string exec(const std::string& cmd) {
72 std::array<char, 128> buffer;
73 ✗ std::string result;
74 ✗ struct PipeCloser { void operator()(FILE* f) const { if (f) pclose(f); } };
75 ✗ std::unique_ptr<FILE, PipeCloser> pipe(popen(cmd.c_str(), "r"));
76 ✗ if (!pipe) {
77 ✗ throw std::runtime_error("popen() failed!");
78 }
79 ✗ while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
80 ✗ result += buffer.data();
81 }
82 ✗ return result;
83 ✗ }
84 ✗ static bool HasI2CDevice(int i, int i2cBus) {
85 char buf[256];
86 ✗ snprintf(buf, sizeof(buf), "i2cdetect -y -r %d 0x%X 0x%X", i2cBus, i, i);
87 ✗ std::string result = exec(buf);
88 ✗ return result.find("--") == std::string::npos;
89 ✗ }
90 ✗ static int findI2CDeviceBus(int i) {
91 ✗ if (HasI2CDevice(i, DEFAULT_I2C_BUS)) {
92 ✗ return DEFAULT_I2C_BUS;
93 }
94 if (DEFAULT_I2C_BUS == 2) {
95 ✗ if (HasI2CDevice(i, 1)) {
96 ✗ return 1;
97 }
98 }
99 ✗ return -1;
100 }
101
102 // commands
103 static constexpr int LCD_CLEARDISPLAY = 0x01;
104 static constexpr int LCD_RETURNHOME = 0x02;
105 static constexpr int LCD_ENTRYMODESET = 0x04;
106 static constexpr int LCD_DISPLAYCONTROL = 0x08;
107 static constexpr int LCD_CURSORSHIFT = 0x10;
108 static constexpr int LCD_FUNCTIONSET = 0x20;
109 static constexpr int LCD_SETCGRAMADDR = 0x40;
110 static constexpr int LCD_SETDDRAMADDR = 0x80;
111
112 // Flags for display on/off control
113 static constexpr int LCD_DISPLAYON = 0x04;
114 static constexpr int LCD_DISPLAYOFF = 0x00;
115 static constexpr int LCD_CURSORON = 0x02;
116 static constexpr int LCD_CURSOROFF = 0x00;
117 static constexpr int LCD_BLINKON = 0x01;
118 static constexpr int LCD_BLINKOFF = 0x00;
119
120 // Flags for display entry mode
121 static constexpr int LCD_ENTRYRIGHT = 0x00;
122 static constexpr int LCD_ENTRYLEFT = 0x02;
123 static constexpr int LCD_ENTRYSHIFTINCREMENT = 0x01;
124 static constexpr int LCD_ENTRYSHIFTDECREMENT = 0x00;
125
126 // Flags for display/cursor shift
127 static constexpr int LCD_DISPLAYMOVE = 0x08;
128 static constexpr int LCD_CURSORMOVE = 0x00;
129 static constexpr int LCD_MOVERIGHT = 0x04;
130 static constexpr int LCD_MOVELEFT = 0x00;
131
132 static std::vector<const PinCapabilities*> LINES;
133 static std::vector<const PinCapabilities*> BACKLIGHT;
134
135 static std::vector<int> VALUES = { 0, 0, 0, 0, 0, 0, 0 };
136 static std::vector<int> BACKLIGHTVALUES;
137 static int BACKLIGHTONVALUE = 1;
138 static int displayshift = 0;
139 static int displaymode = 0;
140 static int displaycontrol = 0;
141
142 ✗ inline void setValues(std::vector<const PinCapabilities*>& p, std::vector<int>& v) {
143 ✗ for (size_t i = 0; i < p.size(); i++) {
144 ✗ p[i]->setValue(v[i]);
145 }
146 ✗ }
147
148 ✗ bool I2C1602_2004_DisplayDriver::initialize(int& i2cBus) {
149 ✗ std::vector<unsigned int> lineOffsets = lineOffsets0x3f;
150 ✗ i2cBus = DEFAULT_I2C_BUS;
151 ✗ int device = 0x3f;
152 ✗ std::vector<unsigned int> blOffset = lineOffset0x3fBacklight;
153 ✗ std::string deviceType = "pcf8574";
154 ✗ if (ledType == LED_TYPE_0x20_1602) {
155 ✗ device = 0x20;
156 // deviceType = "pca9675"; // maybe?
157 ✗ deviceType = "mcp23017";
158 ✗ lineOffsets = lineOffsets0x20;
159 ✗ blOffset = lineOffset0x20Backlight;
160 ✗ BACKLIGHTONVALUE = 0;
161 }
162 ✗ if (ledType == LED_TYPE_0x27_PCF_1602 || ledType == LED_TYPE_0x27_PCF_2004) {
163 ✗ device = 0x27;
164 ✗ deviceType = "pcf8574";
165 }
166 ✗ i2cBus = findI2CDeviceBus(device);
167 ✗ if (i2cBus == -1) {
168 ✗ return false;
169 }
170 char buf[128];
171 ✗ snprintf(buf, sizeof(buf), "/sys/bus/i2c/devices/i2c-%d/new_device", i2cBus);
172 ✗ int f = open(buf, O_WRONLY);
173 ✗ snprintf(buf, sizeof(buf), "%s 0x%02X", deviceType.c_str(), device);
174 ✗ write(f, buf, strlen(buf));
175 ✗ close(f);
176 ✗ sleep(1);
177
178 ✗ PinCapabilities::InitGPIO("FPPOLED", new PLAT_GPIO_CLASS());
179
180 ✗ for (auto a : lineOffsets) {
181 ✗ std::string pn = deviceType + "-" + std::to_string(a);
182 ✗ const PinCapabilities& p = PinCapabilities::getPinByName(pn);
183 ✗ if (p.ptr()) {
184 ✗ LINES.push_back(p.ptr());
185 ✗ p.configPin("gpio", true, "FPPOLED - LCD Data");
186 ✗ p.setValue(0);
187 }
188 ✗ }
189 ✗ for (auto& a : blOffset) {
190 ✗ std::string pn = deviceType + "-" + std::to_string(a);
191 ✗ const PinCapabilities& p = PinCapabilities::getPinByName(pn);
192 ✗ if (p.ptr()) {
193 ✗ p.configPin("gpio", true, "FPPOLED - LCD Backlight");
194 ✗ p.setValue(BACKLIGHTONVALUE ? 0 : 1);
195 ✗ BACKLIGHT.push_back(p.ptr());
196 }
197 ✗ }
198
199 ✗ displayshift = (LCD_CURSORMOVE | LCD_MOVERIGHT);
200 ✗ displaymode = (LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT);
201 ✗ displaycontrol = (LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF);
202 ✗ command(0x33);
203 ✗ command(0x32); // 4bit
204 ✗ command(0x28); // function set
205
206 ✗ command(LCD_DISPLAYCONTROL | displaycontrol);
207 ✗ command(LCD_CURSORSHIFT | displayshift);
208 ✗ command(LCD_CLEARDISPLAY);
209 ✗ std::this_thread::sleep_for(std::chrono::microseconds(1000)); // takes a long time
210
211 ✗ command(LCD_ENTRYMODESET | displaymode);
212
213 ✗ for (int x = 0; x < BACKLIGHT.size(); x++) {
214 ✗ BACKLIGHTVALUES.push_back(BACKLIGHTONVALUE ? 1 : 0);
215 }
216 ✗ setValues(BACKLIGHT, BACKLIGHTVALUES);
217 ✗ setValues(LINES, VALUES);
218 ✗ return true;
219 ✗ }
220
221 ✗ void I2C1602_2004_DisplayDriver::command(uint8_t bits) {
222 ✗ send(bits, true);
223 ✗ }
224 ✗ void I2C1602_2004_DisplayDriver::send(uint8_t bits, bool cmd) {
225 ✗ uint8_t highnib = (bits & 0xf0) >> 4;
226 ✗ uint8_t lownib = bits & 0xf;
227 ✗ write4Bits(highnib, cmd);
228 ✗ write4Bits(lownib, cmd);
229 ✗ }
230
231 ✗ static void printValues() {
232 ✗ int val = 0;
233 ✗ for (int x = 0; x < 8; x++) {
234 ✗ if (VALUES[RS_PIN])
235 ✗ val |= 0x80;
236 ✗ if (VALUES[E_PIN])
237 ✗ val |= 0x20;
238 ✗ if (VALUES[RW_PIN])
239 ✗ val |= 0x1;
240 ✗ if (VALUES[D7_PIN])
241 ✗ val |= 0x2;
242 ✗ if (VALUES[D6_PIN])
243 ✗ val |= 0x4;
244 ✗ if (VALUES[D5_PIN])
245 ✗ val |= 0x8;
246 ✗ if (VALUES[D4_PIN])
247 ✗ val |= 0x10;
248 }
249 ✗ for (int i = 8; i; i--) {
250 ✗ putchar('0' + ((val >> (i - 1)) & 1));
251 }
252 ✗ putchar(' ');
253 ✗ }
254
255 ✗ void I2C1602_2004_DisplayDriver::write4Bits(uint8_t bits, bool cmd) {
256 ✗ VALUES[RS_PIN] = cmd ? 0 : 1;
257 ✗ VALUES[D4_PIN] = (bits & 0x1) ? 1 : 0;
258 ✗ VALUES[D5_PIN] = (bits & 0x2) ? 1 : 0;
259 ✗ VALUES[D6_PIN] = (bits & 0x4) ? 1 : 0;
260 ✗ VALUES[D7_PIN] = (bits & 0x8) ? 1 : 0;
261 ✗ VALUES[E_PIN] = 1;
262 ✗ setValues(LINES, VALUES);
263 ✗ std::this_thread::sleep_for(std::chrono::microseconds(50)); // need > 37us to settle
264 ✗ VALUES[E_PIN] = 0;
265 ✗ setValues(LINES, VALUES);
266 ✗ std::this_thread::sleep_for(std::chrono::nanoseconds(500)); // enable pulse must be >450ns
267 // putchar('\n');
268 ✗ }
269
270 ✗ void I2C1602_2004_DisplayDriver::printString(int x, int y, const std::string& str2, bool white) {
271 ✗ int width = getWidth() / 6;
272 ✗ int row = y / 8;
273 ✗ int col = x / 6;
274 ✗ std::string str = str2;
275 ✗ if (!white) {
276 ✗ str = "<" + str + ">";
277 }
278 ✗ for (int i = 0; i < str.length(); i++) {
279 ✗ if ((col + i) < width) {
280 ✗ lines[row][col + i] = str[i];
281 }
282 }
283 ✗ }
284 ✗ void I2C1602_2004_DisplayDriver::printStringCentered(int y, const std::string& str2, bool white) {
285 ✗ int width = getWidth() / 6;
286 ✗ std::string str = str2;
287 ✗ if (!white) {
288 ✗ str = "<" + str + ">";
289 }
290 ✗ int row = y / 8;
291 ✗ int col = (width - str.length()) / 2;
292 ✗ for (int i = 0; i < str.length(); i++) {
293 ✗ if ((col + i) < width) {
294 ✗ lines[row][col + i] = str[i];
295 }
296 }
297 ✗ }
298 ✗ void I2C1602_2004_DisplayDriver::fillTriangle(short x0, short y0, short x1, short y1, short x2, short y2, bool white) {
299 ✗ }
300 ✗ void I2C1602_2004_DisplayDriver::drawLine(short x0, short y0, short x1, short y1, bool white) {
301 ✗ }
302 ✗ void I2C1602_2004_DisplayDriver::drawBitmap(short x, short y, const unsigned char bitmap[], short w, short h, bool white) {
303 ✗ }
304 ✗ void I2C1602_2004_DisplayDriver::drawRect(short x0, short y0, short x1, short y1, bool white) {
305 ✗ }
306 ✗ void I2C1602_2004_DisplayDriver::fillRect(short x0, short y0, short x1, short y1, bool white) {
307 ✗ }
308 ✗ void I2C1602_2004_DisplayDriver::clearDisplay() {
309 ✗ int width = getWidth() / 6;
310 ✗ std::string blank;
311 ✗ blank.append(width, ' ');
312 ✗ for (auto& a : lines) {
313 ✗ a = blank;
314 }
315 ✗ if (!(displaycontrol & LCD_DISPLAYON)) {
316 ✗ for (auto& a : BACKLIGHTVALUES) {
317 ✗ a = BACKLIGHTONVALUE;
318 }
319 ✗ setValues(BACKLIGHT, BACKLIGHTVALUES);
320 ✗ displaycontrol |= LCD_DISPLAYON;
321 ✗ command(LCD_DISPLAYCONTROL | displaycontrol);
322 }
323 ✗ }
324 ✗ void I2C1602_2004_DisplayDriver::displayOff() {
325 ✗ if (displaycontrol & LCD_DISPLAYON) {
326 ✗ for (auto& a : BACKLIGHTVALUES) {
327 ✗ a = BACKLIGHTONVALUE ? 0 : 1;
328 }
329 ✗ setValues(BACKLIGHT, BACKLIGHTVALUES);
330 ✗ displaycontrol &= ~LCD_DISPLAYON;
331 ✗ command(LCD_DISPLAYCONTROL | displaycontrol);
332 }
333 ✗ }
334 ✗ void I2C1602_2004_DisplayDriver::flushDisplay() {
335 ✗ int row_offsets[] = { 0x80, 0xC0, 0x94, 0xD4 };
336 ✗ int width = getWidth() / 6;
337 ✗ int numRows = 4;
338 ✗ if (ledType != LED_TYPE_0x3f_2004) {
339 ✗ numRows = 2;
340 }
341 ✗ for (int row = 0; row < 4; row++) {
342 ✗ if (width > lines[row].length()) {
343 ✗ lines[row].append(width - lines[row].length(), ' ');
344 }
345 ✗ if (lines[row].size() > width) {
346 ✗ lines[row] = lines[row].substr(0, width);
347 }
348 ✗ if (lines[row] != lastLines[row]) {
349 ✗ command(row_offsets[row]);
350 ✗ for (int p = 0; p < lines[row].length(); p++) {
351 ✗ uint8_t t = lines[row][p];
352 ✗ send(lines[row][p], false);
353 }
354 ✗ lastLines[row] = lines[row];
355 }
356 }
357 ✗ }
358
359 ✗ bool I2C1602_2004_DisplayDriver::setFont(const std::string& f) {
360 ✗ return false;
361 }
362