Артефакты на lcd при добавлении, казалось бы, не связанного кода
У меня есть эта простая программа, в которой я реализую структуру меню на OLED-дисплее и поворотном энкодере.
Код работает функционально, как и предполагалось, однако, когда я добавляю дополнительные пункты меню, я получаю некоторые артефакты на дисплее в правом нижнем углу (см. Скриншоты).
Код, на который я ссылаюсь, находится внутри комментария блока в initMenu()
.
Когда я раскомментирую этот блок, артефакты начинают появляться каждый раз, когда я поворачиваю ручку на кодере. Иногда она временно исчезает или слегка меняет свою форму.
Как я уже сказал, в остальном код ведет себя так, как и ожидалось.
Может ли кто-нибудь пролить свет на то, почему это происходит?
#define DEBUG 1
// Display
#include "Adafruit_GFX.h"
#include "Adafruit_SSD1306.h"
#define OLED_RESET -1
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display;
// Rotary encoder
#include "RotaryEncoder.h"
#include "YetAnotherPcInt.h"
#define RTRY_ENC_SW A1
#define RTRY_ENC_CLK A2
#define RTRY_ENC_DT A3
RotaryEncoder encoder(RTRY_ENC_CLK, RTRY_ENC_DT);
void onSwitch();
void onRotate();
typedef struct MenuItem {
String name;
byte id = 0;
boolean active = false;
MenuItem* prevSibling;
MenuItem* nextSibling;
MenuItem* currentChild;
};
MenuItem autoModes;
MenuItem manualModes;
MenuItem settings;
MenuItem peakAverage;
MenuItem peakToPeak;
MenuItem rms;
MenuItem* currentItem;
MenuItem* activeMode;
void setup() {
#ifdef DEBUG
Serial.begin(115200);
#endif
initEncoder();
initMenu();
initDisplay();
}
boolean hasInputs = false;
boolean switchPressed = false;
void loop() {
if (hasInputs == true) {
hasInputs = false;
handleButtonInputs();
}
}
void initEncoder() {
pinMode(RTRY_ENC_SW, INPUT_PULLUP);
PcInt::attachInterrupt(RTRY_ENC_SW, onSwitch, FALLING);
PcInt::attachInterrupt(RTRY_ENC_CLK, onRotate, CHANGE);
PcInt::attachInterrupt(RTRY_ENC_DT, onRotate, CHANGE);
}
void initMenu() {
autoModes.name = "Auto";
autoModes.id = 1;
autoModes.active = true;
autoModes.prevSibling = &settings;
autoModes.nextSibling = &manualModes;
autoModes.currentChild = &peakAverage;
manualModes.name = "Manual";
manualModes.id = 2;
manualModes.prevSibling = &autoModes;
manualModes.nextSibling = &settings;
settings.name = "Config";
settings.id = 3;
settings.prevSibling = &manualModes;
settings.nextSibling = &autoModes;
/*
peakAverage.name = "PAvg";
peakAverage.id = 4;
peakAverage.active = true;
peakAverage.prevSibling = &rms;
peakAverage.nextSibling = &peakToPeak;
peakToPeak.name = "P2P";
peakToPeak.id = 5;
peakToPeak.prevSibling = &peakAverage;
peakToPeak.nextSibling = &rms;
rms.name = "RMS";
rms.id = 6;
rms.prevSibling = &peakToPeak;
rms.nextSibling = &peakAverage;
activeMode = &peakAverage;
*/
currentItem = &autoModes;
}
void initDisplay() {
display = Adafruit_SSD1306(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
display.setCursor(36, 24);
display.setTextSize(2);
display.setTextColor(1);
display.print("Hello");
display.display();
}
void handleButtonInputs() {
if (switchPressed) {
switchPressed = false;
if (currentItem->id <= 3) {
currentItem = currentItem->currentChild;
printMenu();
} else if (4 <= currentItem->id && currentItem->id <= 6) {
if (activeMode->id != currentItem->id) {
activeMode->active = false;
currentItem->active = true;
activeMode = currentItem;
}
currentItem = &autoModes;
printMenu();
}
}
RotaryEncoder::Direction direction = encoder.getDirection();
if (direction == RotaryEncoder::Direction::CLOCKWISE) {
#ifdef DEBUG
Serial.println("clockwise");
#endif
currentItem = currentItem->nextSibling;
printMenu();
} else if (direction == RotaryEncoder::Direction::COUNTERCLOCKWISE) {
#ifdef DEBUG
Serial.println("counterclockwise");
#endif
currentItem = currentItem->prevSibling;
printMenu();
} else if (direction == RotaryEncoder::Direction::NOROTATION ) {
#ifdef DEBUG
Serial.println("no rotation");
#endif
// ?
}
}
void printMenu() {
#ifdef DEBUG
Serial.println(currentItem->prevSibling->name);
#endif
display.clearDisplay();
display.setTextSize(2);
Serial.println(currentItem->prevSibling->name);
int x1 = 64 - currentItem->prevSibling->name.length() * 6;
if (currentItem->prevSibling->active) {
display.setTextColor(0, 1);
display.fillRect(x1 - 2, 0, 2, 16, 1);
} else {
display.setTextColor(1, 0);
}
display.setCursor(x1, 0);
display.print(currentItem->prevSibling->name);
#ifdef DEBUG
Serial.println(currentItem->name);
#endif
int x2 = 64 - currentItem->name.length() * 6;
int width = currentItem->name.length() * 12 + 10;
if (currentItem->active) {
display.setTextColor(0, 1);
display.fillRect(x2 - 6, 19, width, 24, 1);
} else {
display.setTextColor(1, 0);
display.drawRect(x2 - 6, 19, width, 24, 1);
}
display.setCursor(x2, 23);
display.print(currentItem->name);
#ifdef DEBUG
Serial.println(currentItem->nextSibling->name);
#endif
int x3 = 64 - currentItem->nextSibling->name.length() * 6;
if (currentItem->nextSibling->active) {
display.setTextColor(0, 1);
display.fillRect(x3 - 2, 47, 2, 16, 1);
} else {
display.setTextColor(1, 0);
}
display.setCursor(x3, 47);
display.print(currentItem->nextSibling->name);
display.display();
}
void onSwitch() {
hasInputs = true;
switchPressed = true;
}
void onRotate() {
hasInputs = true;
encoder.tick();
}
Это результат процесса компиляции, касающийся размера программы:
Sketch uses 18198 bytes (59%) of program storage space. Maximum is 30720 bytes.
Global variables use 827 bytes (40%) of dynamic memory, leaving 1221 bytes for local variables. Maximum is 2048 bytes.
@Thomas Hirsch, 👍0
Обсуждение1 ответ
Лучший ответ:
Спасибо за комментарии об использовании памяти, я смог решить эту конкретную проблему.
Я обновил свою программу и оптимизировал ее, разделив отображение меню, используя, где это возможно, переменные меньшего размера и поместив все строки в PROGMEM с помощью макроса F ()
.
Я также заглянул на эту страницу https://learn.adafruit.com/memories-of-an-arduino/measuring-free-memory и скопировал оттуда freeMemory ():
#define DEBUG 1
#define DEBUG_MEMORY 1
// Дисплей
#include "Adafruit_GFX.h"
#include "Adafruit_SSD1306.h"
#define OLED_RESET -1
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display;
// Поворотный энкодер
#include "RotaryEncoder.h"
#include "YetAnotherPcInt.h"
#define RTRY_ENC_SW A1
#define RTRY_ENC_CLK A2
#define RTRY_ENC_DT A3
RotaryEncoder encoder(RTRY_ENC_CLK, RTRY_ENC_DT);
void onSwitch();
void onRotate();
typedef struct MenuItem {
String name;
byte id = 0;
boolean active = false;
MenuItem* prevSibling;
MenuItem* nextSibling;
MenuItem* currentChild;
};
MenuItem autoModes;
MenuItem manualModes;
MenuItem settings;
MenuItem peakAverage;
MenuItem peakToPeak;
MenuItem rms;
MenuItem* currentItem;
MenuItem* activeMode;
#ifdef DEBUG_MEMORY
#ifdef __arm__
// следует использовать uinstd.h для определения sbrk
extern "C" char* sbrk(int incr);
#else // __ARM__
extern char *__brkval;
#endif // __arm__
int freeMemory() {
char top;
#ifdef __arm__
return &top - reinterpret_cast<char*>(sbrk(0));
#elif defined(CORE_TEENSY) || (ARDUINO > 103 && ARDUINO != 151)
return &top - __brkval;
#else // __arm__
return __brkval ? &top - __brkval : &top - __malloc_heap_start;
#endif // __arm__
}
#endif
void setup() {
#ifdef DEBUG
Serial.begin(115200);
#endif
initEncoder();
initMenu();
initDisplay();
}
boolean hasInputs = false;
boolean switchPressed = false;
void loop() {
if (hasInputs == true) {
hasInputs = false;
handleButtonInputs();
}
}
void initEncoder() {
pinMode(RTRY_ENC_SW, INPUT_PULLUP);
PcInt::attachInterrupt(RTRY_ENC_SW, onSwitch, FALLING);
PcInt::attachInterrupt(RTRY_ENC_CLK, onRotate, CHANGE);
PcInt::attachInterrupt(RTRY_ENC_DT, onRotate, CHANGE);
}
void initMenu() {
autoModes.name = F("Auto");
autoModes.id = 1;
autoModes.active = true;
autoModes.prevSibling = &settings;
autoModes.nextSibling = &manualModes;
autoModes.currentChild = &peakAverage;
manualModes.name = F("Manual");
manualModes.id = 2;
manualModes.prevSibling = &autoModes;
manualModes.nextSibling = &settings;
settings.name = F("Config");
settings.id = 3;
settings.prevSibling = &manualModes;
settings.nextSibling = &autoModes;
peakAverage.name = F("PAvg");
peakAverage.id = 4;
peakAverage.active = true;
peakAverage.prevSibling = &rms;
peakAverage.nextSibling = &peakToPeak;
peakToPeak.name = F("P2P");
peakToPeak.id = 5;
peakToPeak.prevSibling = &peakAverage;
peakToPeak.nextSibling = &rms;
rms.name = F("RMS");
rms.id = 6;
rms.prevSibling = &peakToPeak;
rms.nextSibling = &peakAverage;
activeMode = &peakAverage;
currentItem = &autoModes;
}
void initDisplay() {
display = Adafruit_SSD1306(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
display.setCursor(36, 24);
display.setTextSize(2);
display.setTextColor(1);
display.print(F("Hello"));
display.display();
}
void handleButtonInputs() {
if (switchPressed) {
switchPressed = false;
if (currentItem->id <= 3) {
currentItem = currentItem->currentChild;
renderMenu();
} else if (4 <= currentItem->id && currentItem->id <= 6) {
if (activeMode->id != currentItem->id) {
activeMode->active = false;
currentItem->active = true;
activeMode = currentItem;
}
currentItem = &autoModes;
renderMenu();
#ifdef DEBUG_MEMORY
Serial.println(freeMemory());
#endif
}
}
RotaryEncoder::Direction direction = encoder.getDirection();
if (direction == RotaryEncoder::Direction::CLOCKWISE) {
#ifdef DEBUG
Serial.println(F("cw"));
#endif
currentItem = currentItem->nextSibling;
renderMenu();
} else if (direction == RotaryEncoder::Direction::COUNTERCLOCKWISE) {
#ifdef DEBUG
Serial.println(F("ccw"));
#endif
currentItem = currentItem->prevSibling;
renderMenu();
} else if (direction == RotaryEncoder::Direction::NOROTATION ) {
#ifdef DEBUG
Serial.println(F("nr"));
#endif
// ?
}
}
void renderMenu() {
display.clearDisplay();
printPrevSibling();
printCurrentItem();
printNextSibling();
#ifdef DEBUG_MEMORY
Serial.println(freeMemory());
#endif
display.display();
}
void printPrevSibling() {
#ifdef DEBUG
Serial.println(currentItem->prevSibling->name);
#endif
display.setTextSize(2);
Serial.println(currentItem->prevSibling->name);
byte x1 = 64 - currentItem->prevSibling->name.length() * 6;
if (currentItem->prevSibling->active) {
display.setTextColor(0, 1);
display.fillRect(x1 - 2, 0, 2, 16, 1);
} else {
display.setTextColor(1, 0);
}
display.setCursor(x1, 0);
display.print(currentItem->prevSibling->name);
}
void printCurrentItem() {
#ifdef DEBUG
Serial.println(currentItem->name);
#endif
byte x2 = 64 - currentItem->name.length() * 6;
byte width = currentItem->name.length() * 12 + 10;
if (currentItem->active) {
display.setTextColor(0, 1);
display.fillRect(x2 - 6, 19, width, 24, 1);
} else {
display.setTextColor(1, 0);
display.drawRect(x2 - 6, 19, width, 24, 1);
}
display.setCursor(x2, 23);
display.print(currentItem->name);
}
void printNextSibling() {
#ifdef DEBUG
Serial.println(currentItem->nextSibling->name);
#endif
byte x3 = 64 - currentItem->nextSibling->name.length() * 6;
if (currentItem->nextSibling->active) {
display.setTextColor(0, 1);
display.fillRect(x3 - 2, 47, 2, 16, 1);
} else {
display.setTextColor(1, 0);
}
display.setCursor(x3, 47);
display.print(currentItem->nextSibling->name);
}
void onSwitch() {
hasInputs = true;
switchPressed = true;
}
void onRotate() {
hasInputs = true;
encoder.tick();
}
Теперь размер такой:
Sketch uses 18588 bytes (60%) of program storage space. Maximum is 30720 bytes.
Global variables use 761 bytes (37%) of dynamic memory, leaving 1287 bytes for local variables. Maximum is 2048 bytes.
Однако улучшение не так уж велико, и мне все еще остается гадать, когда возникнет следующая проблема. Я все еще благодарен за дальнейшие идеи, помимо использования большего контроллера, потому что я еще не совсем закончил с этим.
Если у кого-нибудь есть еще способы улучшить эту программу без ущерба для производительности, так как я добавлю радио и микрофон, я приму это как ответ на этот вопрос.
Показывает ли многократная печать свободной памяти что-то подозрительное? У вас осталось совсем немного оперативной памяти, и, похоже, у вас нет рекурсивного кода, поэтому мне интересно, где будет переполняться память. Я также не вижу выделения динамической памяти., @PMF
@PMF: библиотека Adafruit SSD1306 выделяет 1 кб оперативной памяти в этой конфигурации для буфера экрана. Делает его очень удобным, но едва ли пригодным для использования на Uno. Вероятно, есть лучшие библиотеки, если все, что нужно, - это распечатать какой-то текст., @Mat
@PMF Да, как указал @Mat, создание экземпляра объекта "дисплей" потребляет много оперативной памяти одновременно. В конце концов я прибегнул к использованию более низких значений "SCREEN_WIDTH" и "SCREEN_HEIGHT"..., @Thomas Hirsch
- Альтернативы дисплею Nextion
- Эффективный рабочий процесс/инструменты для преобразования цветных изображений в шестнадцатеричные массивы
- Ошибка: invalid application of 'sizeof' to incomplete type 'int []' при попытке вычислить размер массива в библиотеке
- Глобальные переменные занимают много места в динамической памяти.
- Запуск 7-контактного OLED-дисплея с 4 контактами (I2C)
- Включить Guards vs #pragma один раз
- Ошибка: "'lcd' does not name a type" при использовании библиотеки LiquidCrystal.
- Нужна помощь с библиотекой U8GLIB
(4 <= currentItem->id <= 6)
is parsed as((4 <= currentItem->id) <= 6)
which is always true since comparisons evaluate to 0 or 1. You need to split it up((4 <= currentItem->id) && (CurrentItem->идентификатор <= 6))
., @MatСколько у вас свободной оперативной памяти?, @Edgar Bonet
Похоже, ваш стек врезается в вашу кучу., @Majenko
@EdgarBonet Я обновил свой вопрос относительно размера. Поможет ли это разделить "printMenu" на более мелкие куски? Поместить все постоянные строки в PROGMEM?, @Thomas Hirsch