Почему этот код для светодиодной матрицы 8x8 не работает как надо?
Есть 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);
}
Я получил код из вики с примерами 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
@Mik, 👍1
Обсуждение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. Программирование случайно
Ваш анализ верен, а мой в предыдущем комментарии ошибочен.
Последний светодиод предпоследнего «изображения»; не очищается, когда последнее «изображение» нарисован. Это связано с тем, что вы очищаете матрицу только при включении светодиода.
Это ваш исходный код:
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
- Сканирование строк и столбцов для управления светодиодной матрицей 8x8
- Shiftout обрабатывает только один сдвиговый регистр за раз
- Как адаптировать код для игры в Змею, которую я пытаюсь создать?
- Проблема с алгоритмом конкатенации символов в матричном светодиоде
- Как использовать SPI на Arduino?
- Как решить проблему «avrdude: stk500_recv(): programmer is not responding»?
- Как создать несколько запущенных потоков?
- Как подключиться к Arduino с помощью WiFi?
добавьте код отладки в вашу программу, @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