Случайные артефакты на 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;
  }
}

, 👍1

Обсуждение

Вы вызвали метод 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(...) и т.п., @Majenko

ButtonStates — это библиотека, которую я написал для устранения дребезга кнопок и распознавания двойных и длинных кликов. Вы можете найти его на платформе. Тем не менее, это должно быть довольно низким потреблением оперативной памяти. Вроде бы сейчас артефакты пропали, но u8g2 попробую в будущей версии! Большое спасибо!!, @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();}
}
,