Почему этот код для светодиодной матрицы 8x8 не работает как надо?

arduino-uno led-matrix

Есть Arduino Uno (Keyestudio KS0078) и светодиодная матрица 788BS 8x8. Я построил схему, как на изображении, и запустил эту программу:

#define MY_PERIOD 200

int displayColumn = 0;
uint32_t tmr1;

unsigned char Text[] = { 0x00, 0x1c, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1c };

void Draw_point(unsigned char x, unsigned char y) {
  clear();
  digitalWrite(x + 2, HIGH);
  digitalWrite(y + 10, LOW);
  delay(1);
}

void show_num(void) {
  unsigned char i, j, data;
  for (i = 0; i < 8; i++) {
    data = Text[i];
    data <<= displayColumn;
    for (j = 0; j < 8; j++) {
      if (data & 0x01) {
        Draw_point(j, i);
      }
      data >>= 1;
    }
  }
}

void setup() {
  Serial.begin(9600);
  int i = 0;
  for (i = 2; i < 18; i++) {
    pinMode(i, OUTPUT);
  }
  clear();
}

void loop() {
  show_num();
  if (millis() - tmr1 >= MY_PERIOD) {
    tmr1 = millis();
    displayColumn++;
    if (displayColumn > 7) {
      displayColumn = 0;
    }
  }
}

void clear() {
  for (int i = 2; i < 10; i++)
    digitalWrite(i, LOW);
  for (int i = 10; i < 18; i++)
    digitalWrite(i, HIGH);
}

circuit

Я получил код из вики с примерами Keyestudio и добавил часть с displayColumn и сдвигом влево каждые 0,2 секунды. Но вместо того, чтобы полностью погаснуть на 7-м этапе, один светодиод (x=0, y=6) продолжает гореть. Даже если я перестану выполнять show_num, когда displayColumn > 7 он горит, пока есть питание. Почему?

UPD: я удалил динамическое смещение в loop и установил displayColumn в 5, 6 и 7 - дисплей показывал правильный результат и был пуст, когда displayColumn было равно 7. Очевидно, ошибка прячется между «переходами».

UPD2: Если я пробую программу с другим символом, ошибка на шаге 7 всегда повторяется с другим светодиодом, который загорелся последним на шаге 6.

UPD3: Отладочные данные по запросу @thebusybee (повторяющиеся блоки удалены)

x,y:2,1 x,y:3,1 x,y:4,1
x,y:1,2 x,y:5,2
x,y:1,3 x,y:5,3
x,y:1,4 x,y:5,4
x,y:1,5 x,y:5,5
x,y:1,6 x,y:5,6
x,y:2,7 x,y:3,7 x,y:4,7
displayColumn: 1
x,y:3,1 x,y:4,1 x,y:5,1
x,y:2,2 x,y:6,2
x,y:2,3 x,y:6,3
x,y:2,4 x,y:6,4
x,y:2,5 x,y:6,5
x,y:2,6 x,y:6,6
x,y:3,7 x,y:4,7 x,y:5,7
displayColumn: 2
x,y:4,1 x,y:5,1 x,y:6,1
x,y:3,2 x,y:7,2
x,y:3,3 x,y:7,3
x,y:3,4 x,y:7,4
x,y:3,5 x,y:7,5
x,y:3,6 x,y:7,6
x,y:4,7 x,y:5,7 x,y:6,7
displayColumn: 3
x,y:5,1 x,y:6,1 x,y:7,1
x,y:4,2
x,y:4,3
x,y:4,4
x,y:4,5
x,y:4,6
x,y:5,7 x,y:6,7 x,y:7,7
displayColumn: 4
x,y:6,1 x,y:7,1
x,y:5,2
x,y:5,3
x,y:5,4
x,y:5,5
x,y:5,6
x,y:6,7 x,y:7,7
displayColumn: 5
x,y:7,1
x,y:6,2
x,y:6,3
x,y:6,4
x,y:6,5
x,y:6,6
x,y:7,7
displayColumn: 6


x,y:7,2
x,y:7,3
x,y:7,4
x,y:7,5
x,y:7,6

displayColumn: 7

//many many empty strings
displayColumn: 8

, 👍1

Обсуждение

добавьте код отладки в вашу программу, @jsotola

@jsotola Я несколько раз пытался получить распечатку x, y из некоторых частей кода, но больше понятия не имею. После шага 6 я не обнаружил ничего, что активировало бы пиксель. Кажется, пиксель все еще активен после предыдущих шагов, но почему так и почему? Мой опыт слишком скуден, чтобы это понять., @Mik

Это не может быть правильно: data = Text[i]; данные <<= displayColumn;. Первое назначение будет перезаписано вторым. Также выглядит странно, что вы также увеличиваете displayColumn в цикле(), а также сдвигаете его влево в функции showum(). Он получит некоторые странные значения. Исходный код, если бы он работал, показывал бы «0». Что вы пытаетесь сделать, прокрутить «0» справа налево по дисплею?, @6v6gt

@6v6gt Второй оператор сдвигает значение в data. Это не простое задание, перезаписывающее первое. Напротив, первый оператор необходим для установки определенного значения в data., @the busybee

@6v6gt да, вроде как. Я не знаю, выглядит это странно или нет, потому что я новичок в Arduino. Но я не вижу логических противоречий в порядке присвоения, да и работает он странно, но почти корректно. Первое присваивание устанавливает следующий байт («строка отображения») в data, второе сдвигает его влево, и каждые 0,2 секунды значение displayColumn увеличивается на 1 («один столбец отображения»). Я пытаюсь немного анимировать исходный код, чтобы лучше понять, как готовить дисплеи., @Mik

Упс. Я не присматривался к оператору слишком внимательно. Для меня код выглядит нормально. Вы можете попробовать замедлить мультиплексирование и поискать какие-либо странные эффекты. Когда я работаю с матричным дисплеем, мне проще определить массив, соответствующий дисплею, загрузить массив, а затем записать его. Таким образом, вы можете проверить промежуточное состояние., @6v6gt

Мне ваш код выглядит нормально, я проверил алгоритм с помощью отладочных отпечатков, но вывода на светодиодную матрицу не было. -- Ваша проводка согласно рисунку тоже выглядит нормально. -- Пожалуйста, [отредактируйте] свой вопрос, чтобы уточнить: как долго горит один ошибочный светодиод? Ваш код повторяется бесконечно. Это происходит?, @the busybee

@thebusybee готово., @Mik

@ 6v6gt Я попробую, но этот способ показался мне слишком многословным. Я запустил тот же код, что и вы сказали, для эксперимента с 7-сегментным дисплеем, но метод с последовательностью байтов мне кажется более интересным и лаконичным, поэтому я намерен разобраться, почему я столкнулся с ошибкой., @Mik

Означает ли это явление, когда нежелательный светодиод остается включенным, когда код постоянно прокручивается? Очевидно, вы остановили цикл, чтобы сделать снимки. При том, как написана функция Draw_point(), где дисплей очищается, а затем включается светодиод, неизбежно, что если где-то есть пауза, последний включенный светодиод останется включенным. В качестве грубого теста вы можете добавить еще одну функциюclear() после задержки(1) в функции Draw_point() или, возможно, после show_num() в цикле()., @6v6gt

Пожалуйста, добавьте такие отладочные отпечатки: 1) Для x и y в Draw_point() без разрыва строки. 2) println() после цикла for над j в show_num(). 3) Для displayColumn после увеличения и ролловера в цикле() с разрывом строки. -- В моем воспроизведении я также добавил флаг, который позволяет избежать множественных выводов, когда displayColumn не изменяется, но пространство здесь ограничено. Можно попробовать вставить то же самое. -- Убедитесь, что в последнем раунде с displayColumn = 7 вывод не происходит. Так и должно быть, потому что в вашем шаблоне не установлен бит 0., @the busybee

Обычно вы не включаете светодиоды задним ходом, чтобы выключить их. Вместо этого вы отключите их драйвер, в случае с Arduino вы измените режим вывода на ввод. Однако я не понимаю, как это может быть объяснением наблюдаемого вами эффекта., @the busybee

@thebusybee добавил, и после седьмого шага действительно нет никаких результатов. Я также попробовал использовать флаг, как вы сказали, но таким образом не могу нормально нарисовать символ: светодиоды просто мигают на мгновение после каждого изменения displayColumn. Похоже, состояние контакта сбрасывает каждую итерацию цикла., @Mik

@ 6v6gt это явление наблюдается, когда прокрутка останавливается и на последнем шаге, а не только тогда, когда прокрутка непрерывна. И да, последний clear() внутри Draw_point выключает светодиод, и теперь код работает так, как нужно. Кажется, я понимаю ваше утверждение о clear(), эта функция должна вызываться перед рендерингом любой строки. Исходный код рисует статический символ, поэтому clear() располагается справа. Я просто перемещаю его в show_num в первую for в качестве первой строки, и теперь прокрутка работает правильно. Вы можете написать свой комментарий как ответ немного подробнее и я проверю его на правильность, @Mik

Хороший. Я рад, что теперь это работает, и это была интересная головоломка. Возможно, еще одно решение — просто переместить clear() из начала Draw_point() в конец. Это также должно остановить «задержку» нежелательного светодиода на 200 мс во время прокрутки. В любом случае, я не могу это проверить, поэтому оставлю вас написать ответ и включить в него код, который лучше всего подходит для вас. Также обратите внимание, что @thebusybee подразумевал, чтобы сделать clear() более эффективным. Не стесняйтесь упоминать мое имя в ответе., @6v6gt

Я загрузил ваш исходный код в симулятор, чтобы опробовать его. К сожалению, в симуляторе нет необработанной точечной матрицы 8x8, и я не могу тратить слишком много времени на ее создание из отдельных светодиодов. Я изменил Draw_point(), чтобы распечатать время (мс), в течение которого горел последний светодиод (если> 5 мс). Он дает такие результаты, как «xLast=7, yLast=6, горит в течение 202 мс», и только тот светодиод, который не совсем соответствует вашему утверждению «один светодиод (x=0, y=6) горит». В любом случае, это здесь: https://wokwi.com/projects/389152026616860673., @6v6gt

@6v6gt Кажется, в исходной схеме примера дисплей используется в «альбомной ориентации» вместо «портретной», и его система координат перевернута. Спасибо, что очень помогли!, @Mik


2 ответа


Лучший ответ:

2

Как выяснилось в ходе обсуждения, Перед рисованием новой строки необходимо очистить дисплей. А поскольку вызов clear() находился в начале Draw_point() и эта функция не использовалась на последнем шаге, очистка не выполнялась перед последним шагом. Это правильно для статического рендеринга, но для прокрутки будет правильно переместить clear() в конец Draw_point(), тогда проект будет работать правильно.

#define MY_PERIOD 200

int displayColumn = 0;
uint32_t tmr1;

unsigned char Text[] = { 0x00, 0x1c, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1c };

void Draw_point(unsigned char x, unsigned char y) {
  digitalWrite(x + 2, HIGH);
  digitalWrite(y + 10, LOW);
  delay(1);
  clear();
}

void show_num(void) {
  unsigned char i, j, data;
  for (i = 0; i < 8; i++) {
    data = Text[i];
    data <<= displayColumn;
    for (j = 0; j < 8; j++) {
      if (data & 0x01) {
        Draw_point(j, i);
      }
      data >>= 1;
    }
  }
}

void setup() {
  Serial.begin(9600);
  int i = 0;
  for (i = 2; i < 18; i++) {
    pinMode(i, OUTPUT);
  }
}

void loop() {
  show_num();
  if (millis() - tmr1 >= MY_PERIOD) {
    tmr1 = millis();
    displayColumn++;
    if (displayColumn > 7) {
      displayColumn = 0;
    }
  }
}

void clear() {
  for (int i = 2; i < 10; i++)
    digitalWrite(i, LOW);
  for (int i = 10; i < 18; i++)
    digitalWrite(i, HIGH);
}

Спасибо пользователям @6v6gt и @thebusybee за помощь в поиске решения.

,

1

В дополнение к вашему ответу это для будущих посетителей, которые хотят узнать больше.

1. Программирование случайно

Ваш анализ верен, а мой в предыдущем комментарии ошибочен.

Последний светодиод предпоследнего «изображения»; не очищается, когда последнее «изображение» нарисован. Это связано с тем, что вы очищаете матрицу только при включении светодиода.

Это ваш исходный код:

void Draw_point(unsigned char x, unsigned char y) {
  clear();
  digitalWrite(x + 2, HIGH);
  digitalWrite(y + 10, LOW);
  delay(1);
}

В вашем решении вы очищаете всю матрицу после задержки, что приводит к ожидаемому эффекту.

Однако вы не проанализировали основу до конца. Вы что-то изменили после случайного предложения, и это «работает». Я видел это много раз.

Мы знаем, что Draw_point() обрабатывает только один светодиод. Таким образом, нам не нужно сбрасывать все контакты в соответствующие уровни выключения: 14 из 16 уже в порядке. Достаточно сделать это для только что включенного единственного светодиода.

void Draw_point(unsigned char x, unsigned char y) {
  digitalWrite(x + 2, HIGH);
  digitalWrite(y + 10, LOW);
  delay(1);
  digitalWrite(x + 2, LOW);
  digitalWrite(y + 10, HIGH);
}

2. Включение светодиодов в обратном направлении

Как я уже говорил в комментарии, вы водите светодиоды матрицы наоборот, когда они выключены . Мы все знаем, что комментарии в большинстве случаев не будут читаться. Поэтому я добавляю это сюда. ;-)

Большинство светодиодов выдерживают некоторое обратное напряжение. Согласно техническому описанию 788BS его абсолютный максимум составляет 5 В. рейтинг. Я бы не осмелился применить это на самом деле.

Все профессионалы управляют своими светодиодными матрицами так, что подается только прямой ток. Все остальные светодиоды вообще не активируются.

Решением здесь является изменение режима контактов.

#define MY_PERIOD 200

int displayColumn = 0;
uint32_t tmr1;

unsigned char Text[] = { 0x00, 0x1c, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1c };

void Draw_point(unsigned char x, unsigned char y) {
  pinMode(x + 2, OUTPUT);
  digitalWrite(x + 2, HIGH);
  pinMode(y + 10, OUTPUT);
  digitalWrite(y + 10, LOW);
  delay(1);
  pinMode(x + 2, INPUT);
  pinMode(y + 10, INPUT);
}

void show_num(void) {
  unsigned char i, j, data;
  for (i = 0; i < 8; i++) {
    data = Text[i];
    data <<= displayColumn;
    for (j = 0; j < 8; j++) {
      if (data & 0x01) {
        Draw_point(j, i);
      }
      data >>= 1;
    }
  }
}

void setup() {
}

void loop() {
  show_num();
  if (millis() - tmr1 >= MY_PERIOD) {
    tmr1 = millis();
    displayColumn++;
    if (displayColumn > 7) {
      displayColumn = 0;
    }
  }
}

Вам не нужно выполнять clear() в setup(), поскольку все контакты изначально находятся в состоянии «input». режим уже.

3. О мерцании

Вы попробовали без delay() и видите только тусклый дисплей. Это правильно, поскольку время горения светодиода короткое по сравнению со временем остальной части программы.

При использовании delay(2) возникает мерцание. Давайте подумаем о времени.

Есть разные «картинки»; во время прокрутки при включении указанного количества светодиодов:

displayColumn Количество включенных светодиодов
0 16
1 16
2 16
3 11
4 9
5 7
6 5
7 0

Поскольку Arduino довольно быстр, мы приблизились к расчетам, пренебрегая остальной частью программы.

Каждый отдельный светодиод отображается в течение 2 мс в соответствии с delay(2), поэтому полная картина суммируется с периодами повторения между (почти) 0 мс (displayColumn = 7) и 32 мс (displayColumn = от 0 до 2). Обратная величина дает нам частоту отображения, многим известен термин «кадры в секунду». или частота кадров. Самая низкая частота кадров — с самым длинным периодом: 1/0,032 с = 31,25 Гц. Наши глаза воспринимают это как мерцание. Кстати, как только "картинка" имеет меньше светодиодов, дисплей перестанет мерцать.

Обычно человеческие глаза не видят мерцания, если частота кадров превышает 50–60 Гц. Конечно, с delay(1) периоды вдвое короче, чем с delay(2). Таким образом, самая низкая частота кадров составляет 62,5 Гц, что достаточно для отсутствия мерцания.

Имейте в виду, что частота кадров падает с увеличением количества включенных светодиодов. Если вы хотите иметь частоту не менее 50 Гц с помощью delay(1), вы не можете иметь более 20 светодиодов: 1/50 Гц = 20 мс, что соответствует 20 светодиодам.

Решением здесь является одновременное отображение всей строки. Поскольку в этом случае нужно упорядочить всего 8 строк, вы можете снова повысить до delay(2): 8 строк * 2 мс < 20 мс. Конкретный код оставлен читателю в качестве упражнения. Имейте в виду, что вам необходимо учитывать максимальные электрические характеристики. Микроконтроллер Arduino имеет ограничения на ток источника.

4. Последнее замечание

Вы можете задаться вопросом, почему связанный ресурс (вики-сайт с примерами Keyestudio) показывает это «не так идеально»; исходный код. Ну, как мы видим по плохому форматированию примера, зарубили его в спешке и без осознания того, что новички будут введены в заблуждение.

Относитесь ко всем подобным ресурсам с большой осторожностью. К сожалению, это абсолютно необходимо. (Это включает и мой ответ.)

,

Вот так я и подумал, какая бы моя программа ни была далека от оптимальной. Я прочитал ваш комментарий о переключении режимов, но решил поискать материалы по этой теме, потому что никогда раньше не видел этого метода, поэтому спасибо за объяснение на рабочем примере! Я также должен отметить, что для вашего первого варианта требуется clear(), выполняемая в setup(). И последний вопрос: почему именно delay(1) работает так, как должно? Никакой задержки - символ тусклый, потому что мы включаем и выключаем его мгновенно, времени слишком мало. Но дисплей начинает мерцать при задержке = 2 и выше. Почему 1?, @Mik