Skip to content

Commit

Permalink
Support multiple LCDDisplays on different displays
Browse files Browse the repository at this point in the history
  • Loading branch information
tttapa committed Oct 27, 2024
1 parent a28a058 commit a449f92
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 32 deletions.
4 changes: 2 additions & 2 deletions mock/Core/Print.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ class Print {
// should be overriden by subclasses with buffering
virtual int availableForWrite() { return 0; }

size_t print(const char[]);
virtual size_t print(const char[]); // Virtual for testing
size_t print(const __FlashStringHelper *);
size_t print(char);
size_t print(unsigned char, int = DEC);
Expand All @@ -78,7 +78,7 @@ class Print {
size_t print(double, int = 2);
size_t print(const Printable &);

size_t println(const char[]);
virtual size_t println(const char[]); // Virtual for testing
size_t println(const __FlashStringHelper *);
size_t println(char);
size_t println(unsigned char, int = DEC);
Expand Down
58 changes: 34 additions & 24 deletions src/Display/MCU/LCDDisplay.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ class LCDDisplay : public DisplayElement {
uint8_t track, PixelLocation loc, uint8_t textSize,
uint16_t color)
: DisplayElement(display), lcd(lcd), bank(&bank), track(track - 1),
line(1), x(loc.x), y(loc.y), size(textSize), color(color) {}
line(1), x(loc.x), y(loc.y), size(textSize), color(color) {
lcd.addSubscriber();
}

/**
* @brief Constructor.
Expand All @@ -67,7 +69,9 @@ class LCDDisplay : public DisplayElement {
uint8_t track, uint8_t line, PixelLocation loc, uint8_t textSize,
uint16_t color)
: DisplayElement(display), lcd(lcd), bank(&bank), track(track - 1),
line(line - 1), x(loc.x), y(loc.y), size(textSize), color(color) {}
line(line - 1), x(loc.x), y(loc.y), size(textSize), color(color) {
lcd.addSubscriber();
}

/**
* @brief Constructor.
Expand All @@ -89,7 +93,9 @@ class LCDDisplay : public DisplayElement {
LCDDisplay(DisplayInterface &display, LCD<> &lcd, uint8_t track,
PixelLocation loc, uint8_t textSize, uint16_t color)
: DisplayElement(display), lcd(lcd), track(track - 1), line(1),
x(loc.x), y(loc.y), size(textSize), color(color) {}
x(loc.x), y(loc.y), size(textSize), color(color) {
lcd.addSubscriber();
}

/**
* @brief Constructor.
Expand All @@ -114,30 +120,34 @@ class LCDDisplay : public DisplayElement {
uint8_t line, PixelLocation loc, uint8_t textSize,
uint16_t color)
: DisplayElement(display), lcd(lcd), track(track - 1), line(line - 1),
x(loc.x), y(loc.y), size(textSize), color(color) {}
x(loc.x), y(loc.y), size(textSize), color(color) {
lcd.addSubscriber();
}

LCDDisplay(const LCDDisplay &) = delete;
~LCDDisplay() { lcd.removeSubscriber(); }

void draw() override {
// If it's a message across all tracks, don't display anything.
if (!separateTracks())
return;

// Determine the track and line to display
uint8_t offset = bank ? bank->getOffset() + track : track;
if (offset > 7)
ERROR(F("Track number out of bounds (") << offset << ')', 0xBA41);
if (line > 1)
ERROR(F("Line number out of bounds (") << line << ')', 0xBA42);

// Extract the six-character substring for this track.
const char *text = lcd.getText() + 7 * offset + 56 * line;
char buffer[7];
strncpy(buffer, text, 6);
buffer[6] = '\0';
// Print it to the display
display.setCursor(x, y);
display.setTextSize(size);
display.setTextColor(color);
display.print(buffer);
if (separateTracks()) {
// Determine the track and line to display
uint8_t offset = bank ? bank->getOffset() + track : track;
if (offset > 7)
ERROR(F("Track out of bounds (") << offset << ')', 0xBA41);
if (line > 1)
ERROR(F("Line out of bounds (") << line << ')', 0xBA42);

// Extract the six-character substring for this track.
const char *text = lcd.getText() + 7 * offset + 56 * line;
char buffer[7];
strncpy(buffer, text, 6);
buffer[6] = '\0';
// Print it to the display
display.setCursor(x, y);
display.setTextSize(size);
display.setTextColor(color);
display.print(buffer);
}
lcd.clearDirty();
}

Expand Down
22 changes: 16 additions & 6 deletions src/MIDI_Inputs/MCU/LCD.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,7 @@ class LCD : public MIDIInputElementSysEx, private LCDCounter {
// Null-terminate the buffer
buffer[BufferSize] = '\0';
// Fill the buffer with spaces
for (uint8_t i = 0; i < BufferSize; i++)
buffer[i] = ' ';
for (uint8_t i = 0; i < BufferSize; i++) buffer[i] = ' ';
}

protected:
Expand Down Expand Up @@ -122,14 +121,16 @@ class LCD : public MIDIInputElementSysEx, private LCDCounter {
}
#endif

dirty = true;
markDirty();

// If this is the only instance, the others don't have to be updated
// anymore, so we return true to break the loop:
return getInstances() == 1;
}

public:
void begin() override { markDirty(); }

/// @name Data access
/// @{

Expand All @@ -141,19 +142,28 @@ class LCD : public MIDIInputElementSysEx, private LCDCounter {
/// @name Detecting changes
/// @{

///
/// Check if the text was updated since the last time the dirty flag was
/// cleared.
bool getDirty() const { return dirty; }
bool getDirty() const { return dirty > 0; }
/// Clear the dirty flag.
void clearDirty() { dirty = false; }
void clearDirty() {
if (dirty > 0)
--dirty;
}
/// Set the dirty counter to the number of subscribers (or one).
void markDirty() { dirty = num_subscribers > 0 ? num_subscribers : 1; }
void addSubscriber() { ++num_subscribers; }
void removeSubscriber() { --num_subscribers; }

/// @}

private:
Array<char, BufferSize + 1> buffer;
uint8_t offset;
Cable cable;
bool dirty = true;
uint8_t dirty = 0;
uint8_t num_subscribers = 0;
};

} // namespace MCU
Expand Down
70 changes: 70 additions & 0 deletions test/MIDI_Inputs/test-MCU_LCD.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include <Display/DisplayInterface.hpp>
#include <Display/MCU/LCDDisplay.hpp>
#include <MIDI_Inputs/MCU/LCD.hpp>

USING_CS_NAMESPACE;
Expand Down Expand Up @@ -132,3 +135,70 @@ TEST(LCDlength, len) {
}
}
}

struct TestDisplay : DisplayInterface {
MOCK_METHOD(void, begin, (), (override));
MOCK_METHOD(void, clear, (), (override));
MOCK_METHOD(void, drawBackground, (), (override));
MOCK_METHOD(void, display, (), (override));
MOCK_METHOD(void, drawPixel, (int16_t, int16_t, uint16_t), (override));
MOCK_METHOD(void, setTextColor, (uint16_t), (override));
MOCK_METHOD(void, setTextSize, (uint8_t), (override));
MOCK_METHOD(void, setCursor, (int16_t, int16_t), (override));
MOCK_METHOD(size_t, write, (uint8_t), (override));
MOCK_METHOD(size_t, print, (std::string_view), ());
MOCK_METHOD(void, drawLine, (int16_t, int16_t, int16_t, int16_t, uint16_t),
(override));
MOCK_METHOD(void, drawFastVLine, (int16_t, int16_t, int16_t, uint16_t),
(override));
MOCK_METHOD(void, drawFastHLine, (int16_t, int16_t, int16_t, uint16_t),
(override));
MOCK_METHOD(void, drawXBitmap,
(int16_t, int16_t, const uint8_t[], int16_t, int16_t, uint16_t),
(override));
MOCK_METHOD(void, fillRect, (int16_t, int16_t, int16_t, int16_t, uint16_t),
(override));
MOCK_METHOD(void, drawCircle, (int16_t, int16_t, int16_t, uint16_t),
(override));
MOCK_METHOD(void, fillCircle, (int16_t, int16_t, int16_t, uint16_t),
(override));
size_t print(const char s[]) override {
return print(std::string_view {s});
}
};

TEST(LCDDisplay, multiple) {
using namespace std::string_view_literals;
MCU::LCD<> lcd(0);
std::vector<uint8_t> sysex = {
0xF0, 0x00, 0x00, 0x66, 0x10, 0x12, 0x00, //
'a', 'b', 'c', 'd', 'e', 'f', ' ', //
'h', 'i', 'j', 'k', 'l', 'm', ' ', //
0xF7,
};
lcd.clearDirty();
testing::NiceMock<TestDisplay> displays[2];
MCU::LCDDisplay lcd_displays[] {
{displays[0], lcd, 1, 1, {0, 0}, 2, 0xFFFF},
{displays[1], lcd, 2, 1, {0, 0}, 2, 0xFFFF},
};
lcd.begin();
for (auto &lcd_display : lcd_displays) EXPECT_TRUE(lcd_display.getDirty());
EXPECT_CALL(displays[0], print(" "sv));
lcd_displays[0].draw();
EXPECT_TRUE(lcd_displays[1].getDirty());
EXPECT_CALL(displays[1], print(" "sv));
lcd_displays[1].draw();
for (auto &lcd_display : lcd_displays) EXPECT_FALSE(lcd_display.getDirty());
MIDIInputElementSysEx::updateAllWith(sysex);
std::string_view content {lcd.getText(), 14};
EXPECT_EQ(content, "abcdef hijklm "sv);
EXPECT_TRUE(lcd.getDirty());
for (auto &lcd_display : lcd_displays) EXPECT_TRUE(lcd_display.getDirty());
EXPECT_CALL(displays[0], print("abcdef"sv));
lcd_displays[0].draw();
EXPECT_TRUE(lcd_displays[1].getDirty());
EXPECT_CALL(displays[1], print("hijklm"sv));
lcd_displays[1].draw();
for (auto &lcd_display : lcd_displays) EXPECT_FALSE(lcd_display.getDirty());
}

0 comments on commit a449f92

Please sign in to comment.