Случайные артефакты на OLED-экране SSD1306
У меня очень странная проблема с экраном. Он подключен через i2c к моему Arduino Nano, и я использую поворотный энкодер для навигации по меню.
При запуске все нормально, но когда я пару раз меняю меню, в правом нижнем углу начинают появляться мерцающие артефакты. У кого-нибудь когда-нибудь был такой локализованный шум?
Я подумал, что, возможно, моя частота обновления была слишком высокой, у меня было всего 10 мс. Но я увеличил его до 50, и это ничего не изменило.
https://youtu.be/riwLRgtHG0g
EDIT: Спасибо за ваш отзыв! Я попробовал другой подход, как вы предложили, и избавился от всех вызовов displayMenu() и нескольких циклов while. Артефактов стало меньше, но они все еще там...
Раньше они возникали постоянно, а теперь они появляются только во время selectValue() и action3(). Как будто это было связано с "ценностью" переменная. Я не понимаю...
Вот обновленный код:
#include <Arduino.h>
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_I2CDevice.h>
#include <math.h>
#include <RotaryEncoder.h>
#include <ButtonStates.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1 // Номер вывода сброса (или -1, если общий вывод сброса Arduino)
#define SCREEN_ADDRESS 0x3C
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
RotaryEncoder encoder(A2, A3); // Поворотный энкодер подключен к A2 и A3
ButtonSwitch button(2); // Кнопка поворотного энкодера
int value = 0; // Значение изменено в одном из меню
int selection = 0; // Положение энкодера
struct menuItem { // Структура, определяющая пункт меню
const char *name; // Имя элемента
void (*action)(); // Указатель, указывающий на функцию, обрабатывающую действие
};
struct menu { // Структура, определяющая меню
char *name; // Название меню
int items; // Количество пунктов в меню
struct menuItem *collection; // Массив, содержащий все пункты меню
};
struct menuItem menu1collection[5]; // Массив для меню
struct menuItem item1, item2, item3, item4, item5; // Пункты меню
struct menu menu1 = { "Today's menu", 5, menu1collection }; // Структура, содержащая всю информацию о меню
struct menuItem menu2collection[4];
struct menuItem item6, item7, item8, item9;
struct menu menu2 = { "Tomorrow's menu", 4, menu2collection };
struct menu currentmenu = menu1;
enum screens { MENU, CHOOSEVAL };
enum screens screen = MENU;
void chooseValue(){
display.clearDisplay();
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 20);
display.write( 17 );
display.setCursor(45, 20);
display.print(selection);
display.print("g");
display.setCursor(115, 20);
display.write( 16 );
display.display();
}
void displayMenu(){ // Функция, отображающая меню, переданное в аргументе
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 5);
display.println(currentmenu.name); // Показать заголовок меню
int div = floor((selection-0.5)/4); // Обработка меню с более чем 4 элементами
for(int i=(4*div+1), j=0 ; i<=currentmenu.items && j<4 ; i++, j++){ // Перебор 4 пунктов меню
if(i==selection){ // Выделяем выбранный элемент
display.fillRect(0, 18+10*j, 128, 10, SSD1306_WHITE);
display.setCursor(20, 20+10*j);
display.setTextColor(SSD1306_BLACK);
display.println(currentmenu.collection[i-1].name);
}
else{
display.setTextColor(SSD1306_WHITE);
display.setCursor(20, 20+10*j);
display.println(currentmenu.collection[i-1].name); // Показать элемент
}
}
display.display();
}
ISR(PCINT1_vect) { // Процедура прерывания для поворотного энкодера
encoder.tick(); // Проверяем состояние энкодера
}
// Функции, обрабатывающие действия для каждого пункта меню ------------------------------------------------------- --------------------------
void action1(){
screen = CHOOSEVAL;
}
void action2(){
currentmenu = menu2;
encoder.setPosition(1);
}
void action3(){
display.clearDisplay();
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE);
display.setCursor(45, 20);
display.print(value);
display.println("g");
display.display();
delay(1000);
}
void action4(){
display.clearDisplay();
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 20);
display.println("Action 4!");
display.display();
delay(500);
}
void action5(){
display.clearDisplay();
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 20);
display.println("Action 5!");
display.display();
delay(500);
}
void action6(){
display.clearDisplay();
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 20);
display.println("Action 6!");
display.display();
delay(500);
}
void action7(){
display.clearDisplay();
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 20);
display.println("Action 7!");
display.display();
delay(500);
}
void action8(){
display.clearDisplay();
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 20);
display.println("Action 8!");
display.display();
delay(500);
}
void action9(){
currentmenu = menu1;
encoder.setPosition(1);
}
// ------------------------------------------------ -------------------------------------------------- ------------------
void handleMenu(){
}
void setup() {
Serial.begin(9600);
// Для создания меню необходимо следующее --------------------------------------------------------- ----------------------------------
item1.name = "Choose a value";
item1.action = action1;
menu1collection[0] = item1;
item2.name = "Go to menu 2";
item2.action = action2;
menu1collection[1] = item2;
item3.name = "Display value";
item3.action = action3;
menu1collection[2] = item3;
item4.name = "Item 4";
item4.action = action4;
menu1collection[3] = item4;
item5.name = "Item 5";
item5.action = action5;
menu1collection[4] = item5;
item6.name = "Item 6";
item6.action = action6;
menu2collection[0] = item6;
item7.name = "Item 7";
item7.action = action7;
menu2collection[1] = item7;
item8.name = "Item 8";
item8.action = action8;
menu2collection[2] = item8;
item9.name = "< Back";
item9.action = action9;
menu2collection[3] = item9;
// ------------------------------------------------ -------------------------------------------------- ------------------
PCICR |= (1 << PCIE1); // Это включает прерывание по изменению контакта 1, которое охватывает аналоговые входные контакты или порт C.
PCMSK1 |= (1 << PCINT10) | (1 << PCINT11); // Это разрешает прерывание для контактов 2 и 3 порта C.
if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) { // SSD1306_SWITCHCAPVCC = внутреннее напряжение дисплея 3,3 В
Serial.println(F("SSD1306 allocation failed"));
for(;;); // Не продолжать, цикл навсегда
}
// Инициализируем отображение
display.clearDisplay();
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 20);
display.println("Loading...");
display.display();
delay(500);
}
void loop() {
switch(screen){
case MENU:
selection = encoder.getPosition(); // Получить позицию энкодера
if (selection < 1) { // Остановить энкодер в минимальной позиции
encoder.setPosition(1);
selection = 1;
} else if (selection > currentmenu.items) { // Остановить энкодер на максимальной позиции (количество элементов минус 1)
encoder.setPosition(currentmenu.items);
selection = currentmenu.items;
}
displayMenu();
if (button.triggerSingle()){
currentmenu.collection[selection-1].action(); // Если обнаружен щелчок, выполняем действие элемента
}
break;
case CHOOSEVAL:
selection = value + encoder.getPosition() * 10;
if (selection < 1) { // Остановить энкодер в минимальной позиции
encoder.setPosition(1);
selection = 1;
}
chooseValue();
if (button.triggerSingle()){
value = selection;
encoder.setPosition(1);
screen = MENU;
}
break;
}
}
@Mugen, 👍1
Обсуждение1 ответ
Это решение, которое я придумал случайно. У меня была проблема со странными искажениями артефактов в правом нижнем углу моего дисплея SSD1306, когда я пытался напечатать прокручиваемую строку текста.
Он выполняет свою работу, поэтому, если кто-то заинтересуется этой темой в будущем, не стесняйтесь протестировать ее:
int x, minX;
oled_dispMidTextScroll(F("SETPOINT REACHED! LONG PRESS LEFT TO SET UP A TIMER"));
void oled_dispMidTextScroll(String str){
char text[50];
int size = 1;
str.toCharArray(text, 50);
display.setTextSize(size);
minX = (-1 * size * 6) * strlen(text); // 12 = 6 (px/char) * 2 (размер текста)
display.setCursor(x,28);
display.print(text);
x -= 4; // скорость
if (x < minX) {x = display.width();}
}
- Программа Arduino, использующая i2c, перестает работать после нескольких раз печати на OLED
- Как перевести Arduino Nano в спящий режим с низким энергопотреблением (<0,05 мА)
- MAX30100 не работает
- Ведомое устройство Arduino с двумя мастерами, использующими одну и ту же шину I2C?
- Библиотека I2C MIFARE RC522
- Не удалось выделить SSD1306 при добавлении константы
- Сброс адреса I2c — MLX90614
- SSD1306 распознается сканером I2C, но не может его отобразить
Вы вызвали метод
displayMenu
во всей программе и рекурсивный сам по себе. Почему? Кроме того, вы используете вечныйwhile (1) {
в методе отображения. Я предлагаю удалить цикл while(1). Единственный вызовdisplayMenu
, который имеет смысл, это вызов в методеloop()
. удалить все остальные. Я бы еще скопировал в цикл код запуска действий с дисплея. Не используйтеdelay()
в методе отображения, который останавливает всю систему. Используйтеmillis()
(пример мигания светодиода без задержки показывает, как это сделать., @Peter Paul KieferТакже удалите время из метода
chooseValue
. Затем переместите его в функциюloop
. Я не уверен, что видел все проблемы, но это было бы хорошим началом. Если это не поможет, отредактируйте вопрос с учетом новых наблюдений., @Peter Paul KieferМне кажется, что ваш стек разбивается о вашу кучу., @Majenko
Спасибо за ваш отзыв @PeterPaulKiefer! Я обновил свой код в исходном сообщении., @Mugen
Я не понимаю ссылку @Majenko, что вы имеете в виду?, @Mugen
Тот факт, что повреждение находится в правом нижнем углу, означает, что оно находится в конце буфера экрана. Это будет (одна из) последних вещей в куче. Стек находится над кучей и растет в нее. Слишком большое количество вызовов вложенных функций и локальных переменных приведет к тому, что стек будет расти вниз, пока он не перекроется с кучей и концом буфера экрана, что приведет к повреждению буфера экрана в правом нижнем углу экрана. Сгладьте свою программу. Уменьшите использование оперативной памяти., @Majenko
О, верно! Боже, я только что превратил все "целые" в "uint8_t", и это почти исчезло. Я думаю, может быть, мои строки могут занимать много памяти. Как я могу оптимизировать это? Я имею в виду, что я привык к тому, что JavaScript обрабатывает их... и здесь я не знаю, как сделать его гибким и удобным в использовании одновременно., @Mugen
Откуда
ButtonStates.h
? Как сказал Маженко, у вас очень близко истощение памяти. Я не смог определить, что это происходит на самом деле, но тогда я не запускал ваш точный код. В конечном счете, какой бы ни была причина, вы можете переключиться на что-то вроде [u8g2](https://github.com/olikraus/u8g2/wiki), чтобы избежать 1-килобайтного malloc, который делает библиотека Adafruit. Насколько я могу судить, у вас осталось около 130 байт при запуске исходного кода., @timemageДля начала используйте
F(...)
вокруг всех строковых литералов в вызовахprint(...)
и т.п., @MajenkoButtonStates — это библиотека, которую я написал для устранения дребезга кнопок и распознавания двойных и длинных кликов. Вы можете найти его на платформе. Тем не менее, это должно быть довольно низким потреблением оперативной памяти. Вроде бы сейчас артефакты пропали, но u8g2 попробую в будущей версии! Большое спасибо!!, @Mugen