Динамическая индикация для самодельной LED матрицы. Как лучше реализовать?
- Войдите на сайт для отправки комментариев
Спаял такую вот матрицу из сорока светодиодов (5 строк, 8 столбцов)

Схематически её можно изобразить так

Принцип действия очень простой – управление столбами через выводы регистра. Управление строками – через усилительные транзисторы, подключенные к отдельным выводам микросхемы Atmega8 с прошитым Arduino бутлоадером.
Подскажите пожалуйста, как лучше реализовать динамическую индикацию для этой матрицы. Хочу сделать, что-то вроде бегущей строки.
Я думаю, вначале нужно написать функции включения./выключения для каждой строки, поскольку полноценное изображение здесь может быть сформировано только динамически (построчно)
В общем, алгоритм, как мне кажется, должен быть таким
1. Загрузить в регистр нужную последовательностей нулей и единиц для первой (в данном случае нулевой) строки.
2. Защелкнуть данные на выводах регистра
3. Включить строку
4. Сделать паузу (несколько миллисекунд).
5. Отключить строку
4. Перейти к следующей сроке (начать загружать для неё нужную последовательностей нулей и единиц).
Правильно ли я рассуждаю?
Подскажите пожалуйста, как сделать так, чтобы при таком алгоритме текст «бежал» вправо или влево???
Вот мой первый наброскок кода - только начал писать нужные функции. Оцените пожалуйста
#define Line_0 2
#define Line_1 3
#define Line_2 4
#define Line_3 5
#define Line_4 6
//Уровень HIGH на соответсвуещем выводе Arduino
//позволяет вкл. элементы соответстующей строки
#define SendData 11
//вывод передачи данных
#define SendShift 12
//вывод передачи сдвига
#define SendStore 8
//вывод "защелкивания" информации
void setup(){
pinMode (Line_0, OUTPUT);
pinMode (Line_1, OUTPUT);
pinMode (Line_2, OUTPUT);
pinMode (Line_3, OUTPUT);
pinMode (Line_4, OUTPUT);
pinMode (SendData, OUTPUT);
pinMode (SendShift, OUTPUT);
pinMode (SendStore, OUTPUT);
//устанавливаем все выводы на выход
}
void loop(){
Line_0_EN();
Line_1_EN();
}
void Line_0_EN() // LINE ENABLE (задействовать линию №0)
{
digitalWrite(Line_0, HIGH);
}
void Line_0_DIS() // LINE DISABLE (отключить линию №0)
{
digitalWrite(Line_0, LOW);
}
void Line_1_EN() // LINE ENABLE (задействовать линию №1
{
digitalWrite(Line_1, HIGH);
}
void Line_1_DIS() // LINE DISABLE (отключить линию №1)
{
digitalWrite(Line_1, LOW);
}
В принципе, да, вся динамическая индикация так и делается.
Только надо отделять друг от друга две задачи:
1)Вывод готового изображения на экран. В идеале, эту задачу можно повесить на прерывание от таймера, которое вызывается один раз в 2 мс и отрисовывает очередную строчку. Это даст частоту одновления в 100 гц - не сильно нагружает МК и одновременно хорошо смотрится для глаза.
2)Собственно подготовка изображения. Выделяете "видеопамять" (5 байт, по одному на строку идеально вам подходят) и в основном цикле ее заполняете необходимой картинкой. В частности, для того, чтобы текст бежал нужно просто применить операцию циклического сдвига к каждому байту, повторяя ее раз в некоторое время типа одной секунды.
Только учтите, что операция digitalWrite занимает достаточно долгое время, поэтому лучше писать в порты напрямую, иначе велик риск все процессорное время убить на отрисовку.
Выделяете "видеопамять" (5 байт, по одному на строку идеально вам подходят) и в основном цикле ее заполняете необходимой картинкой. В частности, для того, чтобы текст бежал нужно просто применить операцию циклического сдвига к каждому байту, повторяя ее раз в некоторое время типа одной секунды.
Я подключил эту LED матрицу к Atmega8. Как в этой микросхеме можно выделить "видеопамять"? Подскажите пожалуйста, откуда её выделять: из FLASH? из RAM? из EEPROM?
Только учтите, что операция digitalWrite занимает достаточно долгое время, поэтому лучше писать в порты напрямую, иначе велик риск все процессорное время убить на отрисовку.
А как можно "писать в порты напрямую"? Подскажите пожалуйста? Я думал, что проще функции digitalWrite ничего вообще нет! Ведь она только выдает 0 или 1 на определённом выводе. Или я ошибаюсь??? Подскажите тогда пожалуйста, какие функции мне лучше использовать для формирования и вывода изображения.
P.S. Заранее спасибо всем ответившим!
Я подключил эту LED матрицу к Atmega8. Как в этой микросхеме можно выделить "видеопамять"? Подскажите пожалуйста, откуда её выделять: из FLASH? из RAM? из EEPROM?
Специальной видеопамяти никакой, конечно нет.
Просто в программе объявить
После чего использовать videoMem[0] для содержимого первой строки, videMem[1] - для второй и тд.
А как можно "писать в порты напрямую"? Подскажите пожалуйста? Я думал, что проще функции digitalWrite ничего вообще нет! Ведь она только выдает 0 или 1 на определённом выводе. Или я ошибаюсь???
http://arduino.ru/Tutorial/Upravlenie_portami_cherez_registry
Функции, конечно, простые, но кроме написания числа в порт им еще надо
1)Преобразовать номар порта в понимании ардуино в регистр и номер бита в понимании атмеги.
2)Проверить, нет ли на "порту" ШИМа
3а)Отключить его если что.
4)Проверить, что мы хотим - писать или читать.
В результате там, где процессору нужно реально один или четыре тактат (в зависимости от того, надо ли нам сохранить остальное содержимое регистра), digitalWrite тратит по самым скромным подсчетам минимум в десять раз медленне.
Подскажите тогда пожалуйста, какие функции мне лучше использовать для формирования и вывода изображения.
Для формирования - смотря что вы хотите, но при таких объемах проще "в голове прикинуть"
Для вывода - http://arduino.ru/Reference/Library/SPI
Просто в цикле выводить по одному байту из videoMem[i] в SPI
Я подключил эту LED матрицу к Atmega8. Как в этой микросхеме можно выделить "видеопамять"? Подскажите пожалуйста, откуда её выделять: из FLASH? из RAM? из EEPROM?
Можно в любой. Но наиболее просто/естественно/быстродействие - конечно в RAM.
А вообще, похоже вы себе тут представили что-то намного более страшно-сложное чем есть на самом деле.
Никакой особой "видеопамяти" в Atmega8 естественно нет. Выделение - это вы просто объявляете массив. И у вас есть две части програмы. Одна занимаете тем что выводит содержимое этого массива на диоды. А другая - тем что меняет содержимое этого массива когда нужно (по нажатию кнопок, по датчикам и т.п.)
Так что, в данном случае "видео-память" - это скорее термин который существует только в голове, а не железе.
Вообщем
это и есть "выделили видео-память".
А как можно "писать в порты напрямую"? Подскажите пожалуйста? Я думал, что проще функции digitalWrite ничего вообще нет! Ведь она только выдает 0 или 1 на определённом выводе. Или я ошибаюсь???
И да и нет. Вы не ошиблись в том что делает digitalWrite "с точки зрения пользователя", но ошиблись в том что поставили знак равенства между "простое поведение,просто пользоватся" и "просто устроено внутри".
Где читать? В шапке сайта, есть ссылка Программирование, там есть раздел "Базовые и полезные знания, необходимые для успешного программирования под платформу Arduino". В котором, в свою очередь Прямое управления выходами через регистры микроконтроллера Atmega
И посмотрите какие-там рядом статьи /примеры есть. Еще парочку вам пригодятся - про сдвиговые, к примеру.. Этим вы избежите будущих посыланий в поиск и документацию.
Но... вы все равно попробуйте поиском тоже попользоватся. И сдвиговые (по имени микрухи и по словам "множим выходы") и матрицы диодов (слова "LED матрица", "динамическая индикация") - не раз обсуждались. Покопайтесь - возможно многие будущие вопросы сами отпадут :)
А еще, по поводу прямой записи в регистры можете почитать AVR. Учебный курс. Устройство и работа портов ввода-вывода ... Тоже полезно для лучшего понимая всей этой кухни.
И возвращаясь к digitalWrite, после прочтения всего что выше, как видите включение ноги в нужно состояние требует выставить правильный бит, в правильном порту. Но... на разных камнях эти порты/биты разные. А скетчи - универсальные. digitalWrite(13,HIGHT) одинаково отработает и на уно и на меге. Именно за счет "интелектуальности" фунции digitalWrite. Которая, в зависимости от платы, сама решит какой порт/бит нужно выставить. Но за эту универсально/разумность/легкую преносимость - нужно платить. Тактами процессора и невозможностью поменять состояние сразу нескольких ног одновременно.
Поэтому когда на важна скорость - приходится откадываться от digitalWrite и самому смотреть в даташит и пин-маппинг что чему соотвествует. Выигрываем в скорости, но теряем в универсальности (при переходе на другой камень - нужно будет править скетч) и легкости читания кода.
вот такая статья оч понравилась, все там доходчиво и ясно, правда только с бОльшей матрицей, но принцип один...
Мне кажеться, что оптимальный вариант - использование двух массивов битов: один - для строк, второй - для столбцов.
Только вот, как правильно объединить их в одно целое для вывода определенного символа на матрицу?
Ведь один байт (для выбора нужных столбцов) очевидно должен передаваться на регистр по SPI, второй байт (для выбора нужной строки) будет неполным! И передаваться он должен напрямую!
В Atmega8 для выбора нужной строки задействованы такие выводы
Как использовать операцию сдвига, чтобы он происходил не до конца, то есть не выходил за пределы задействованных выводов?
Может кто-нибудь подскажет?
вот такая статья оч понравилась, все там доходчиво и ясно, правда только с бОльшей матрицей, но принцип один...
Спасибо за ссылку! Статья действительно хорошая. Но если действовать по тому же принципу, придётся использовать два регистра сдвига (один для столбцов, другой - для строк).
Я же хочу довести до ума пример, где нужные светодиоды в столбцах матрицы выбираються через регистр сдвига, а нужная строка - появлением логической единицы на соответствующем выводе Atmega8.
Перебирать строки, я думаю, проще всего с помощью массива
#define SendData 11 //вывод передачи данных #define SendShift 12 //вывод передачи сдвига #define SendStore 8 //вывод "защелкивания" информации void setup(){ DDRD = DDRD | B01111100; PORTD = B01111100; pinMode (SendData, OUTPUT); pinMode (SendShift, OUTPUT); pinMode (SendStore, OUTPUT); //устанавливаем все выводы на выход } void loop(){ byte rowsArray[] {b00000100, b00001000, b00010000, b00100000, 01000000}; // засовываем номера строк в массив byte rowsCounter; // счетчик строк for (rowsCounter=0; rowsCounter < 5; rowsCounter+1) // перебираем // номера строк из массива { ......//включить нужные для отображения символа светодиоды } }а вот как вставлять в эти строки нужные значения битов, так чтобы символы могли "бежать", я пока не додумался.
Допустим, символ - это поле 5х5 точек. Значит мне нужно создать массив типа byte. Но вот как "запихивать" в него нужные значения? Нужно создавать какую-то таблицу знакогенератора? Подскажите пожалуйста!!!
вот такое я писАл
"занкогенеротор" так сказать в строках 4 и 5
ну а запихивать в него типа
matrix[1]=5;
matrix[2]=8;
ну и т.д. это в моем скетче...
Вам в регистр нужно отправлять только одно слово (1 или 2 или 4 или 8 или 16 и т.д. т.е. перебирайте столбцы в двоичном коде), а на строки с выводов дуньки, то есть правильно мыслите... т.е. создаете цикл в котором поочереди перебираются столбцы и отправляются в регистр, ну и во время выбора первого столбца отправляйте на выводы дуньки из массива знакогенератора первое слово, во время выбора второго столбца - второе слово знакогенератора... ну и т.д...
Написал две функции - одну для вывыда столбцов, другую - для строк. Код стал выгледеть так:
#define SendData 11 //вывод передачи данных #define SendShift 12 //вывод передачи сдвига #define SendStore 8 //вывод "защелкивания" информации byte rowsArray[] = {B00000100, B00001000, B00010000, B00100000, B01000000}; byte collsArray[] = {B00000001, B00000001, B00000001, B00000000, B00000001}; byte collsCounter; byte rowsCounter; void setup(){ DDRD = DDRD | B01111100; pinMode (SendData, OUTPUT); pinMode (SendShift, OUTPUT); pinMode (SendStore, OUTPUT); //устанавливаем все выводы на выход } void loop() { strokaLED(){ stolbetsLED(); } delay(20); } void strokaLED(){ for (rowsCounter = 0; rowsCounter < 5; rowsCounter++) {PORTD = rowsArray[rowsCounter]; LED = rowsArray[rowsCounter] } } void stolbetsLED(byte collsArray[], byte collsCounter){ for (collsCounter = 0; collsCounter < 8; collsCounter++){ digitalWrite (SendStore, LOW); shiftOut (SendData, SendShift, LSBFIRST, collsArray[collsCounter]); digitalWrite (SendStore, HIGH); } }Но компилятор почему-то ругаеться на строки 28, 29! Он выдает: too few arguments...
Может быть это из-за того, что я "запихнул" одну функцию в другую??? Подскажите пожалуйста, может кто-нибудь знает???
Ради интереса - посмотрите на строку 44 и попробуйте найти различия со строкой 29.
Их там два. Компилятор оба этих отличия находит, теперь дело за вами.
Далее - что должны символизировать фигурные скобки в строках 28 (открывающая) и 30 (закрывающая)?
И почему оператор в строке 28 не завершен, как и полагается, символом ";"? Чего вы хотели этим добиться? Конструкция настолько необычная (наверняка, кроме ругательства на слишком малое число аргументов у вас далее еще есть сообщение что-то вроде "error: expected `;' before '{' token"), что без ваших объяснений трудно понять ее, ну и, соответственно, порекомендовать правильный вариант исправления (чтобы не только синтаксически верно было, но и задачу решало).
Спасибо за то, что указали на ошибки!
Я поправил код! Теперь он выглядит так:
#define SendData 11 //вывод передачи данных #define SendShift 12 //вывод передачи сдвига #define SendStore 8 //вывод "защелкивания" информации byte rowsArray[] = {B00000100, B00001000, B00010000, B00100000, B01000000}; // инициализация //нужных выводов порта микроконтроллера для перебора строк byte collsArray[] = {B00000001, B00000001, B00000001, B00000000, B00000001}; // код символа "i" (взят для примера) byte collsCounter; // счетчик для выбора столбцов byte rowsCounter; // счетчик для выбора строк void strokaLED (byte rowsArray[], byte rowsCounter){ for (rowsCounter = 0; rowsCounter < 5; rowsCounter++){ PORTD=rowsArray[rowsCounter]; } //функция поочередно } //перебирающая строки void stolbetsLED(byte collsArray[], byte collsCounter){ digitalWrite (SendStore, LOW); // подготовка к защелкиванию //значений на выходах регистра for (collsCounter = 0; collsCounter < 8; collsCounter++){ shiftOut (SendData, SendShift, LSBFIRST, collsArray[collsCounter]); //отправка значений в регистр } digitalWrite (SendStore, HIGH); //защелкивание //значений на выходах регистра } void setup(){ DDRD = DDRD | B01111100; //определяем выводы порта, которые //будут управлять строками - включаем их на выход pinMode (SendData, OUTPUT); //вывод для отправки данных pinMode (SendShift, OUTPUT); //вывод для сдвига //(продвижения битов в регистр) pinMode (SendStore, OUTPUT);//вывод для //защелкивания значений в регистре } void loop() { stolbetsLED; //загружаем значения в регистр для отображения столбца strokaLED; // вкл. соответствующую строку delay(20); //кратковременная пауза // и т.д. загружаем в регистр значения, включаем нужную строку... }Теперь компилятор не ругается, но нужный символ на матрице все равно не появляеться!
P.S.: Я очень детально прокомментировал свой код, пожалуйста, кому не лень, посмотрите, что в нём может быть неправильно!
Одно непонятно, зачем смешивать использование DDRD, digitalWrite и т.п. Это называется искать проблемы
digitalWrite используеться для трех выводов порта С. Разве он имеет какое-то отношения к порту D, для которого я использовал прямую запись в порт
Разве применение такого способа в паре с digitalWrite может привести к неправильному компилированию? Мне кажеться, ошибка в чём-то другом.
Может быть кто-то из специалистов подскажет, что я написал в коде неправильно?
#define SendData 11 //вывод передачи данных #define SendShift 12 //вывод передачи сдвига #define SendStore 8 //вывод "защелкивания" информации byte rowsArray[] = {B00000100, B00001000, B00010000, B00100000, B01000000}; // инициализация //нужных выводов порта микроконтроллера для перебора строк byte collsArray[] = {B00000001, B00000001, B00000001, B00000000, B00000001}; // код символа "i" (взят для примера) byte collsCounter; // счетчик для выбора столбцов byte rowsCounter; // счетчик для выбора строк void strokaLED (byte rowsArray[], byte rowsCounter){ //функция поочередно for (rowsCounter = 0; rowsCounter < 5; rowsCounter++){ //перебирающая строки PORTD=rowsArray[rowsCounter];// выбираем из массива, какую строку включить} } void stolbetsLED(byte collsArray[], byte collsCounter){ digitalWrite (SendStore, LOW); // подготовка к защелкиванию //значений на выходах регистра for (collsCounter = 0; collsCounter < 8; collsCounter++){ shiftOut (SendData, SendShift, LSBFIRST, collsArray[collsCounter]); //отправка значений в регистр } digitalWrite (SendStore, HIGH); //защелкивание //значений на выходах регистра } void setup(){ DDRD = DDRD | B01111100; //определяем выводы порта, которые //будут управлять строками - включаем их на выход pinMode (SendData, OUTPUT); //вывод для отправки данных pinMode (SendShift, OUTPUT); //вывод для сдвига //(продвижения битов в регистр) pinMode (SendStore, OUTPUT);//вывод для //защелкивания значений в регистре } void loop() { stolbetsLED; //загружаем значения в регистр для отображения столбца strokaLED; // вкл. соответствующую строку delay(20); //кратковременная пауза // и т.д. загружаем в регистр значения, включаем нужную строку... }Может быть кто-то из специалистов подскажет, что я написал в коде неправильно?
void loop() {
stolbetsLED; //загружаем значения в регистр для отображения столбца
strokaLED; // вкл. соответствующую строку
delay(20); //кратковременная пауза // и т.д. загружаем в регистр значения, включаем нужную строку... }
Всё не смотрел, но функции надо вызывать с аргументами, если их нет, то скобка всё равно должна быть.
stolbetsLED(); - вот так.
и ваще, вы не правильно написали функции...
voidstolbetsLED(bytecollsArray[],bytecollsCounter){так вот этот аргумент collsArray совсем не тот, что вы описали выше, это заново созданный указатель, не имеющий отношения к предыдущему.
...и ваще, вы не правильно написали функции...
void stolbetsLED(byte collsArray[], byte collsCounter){так вот этот аргумент collsArray совсем не тот, что вы описали выше, это заново созданный указатель, не имеющий отношения к предыдущему.
А куда же тогда он указывает? И как обратиться к ранее созданной переменной (или массиву)? Или это невозможно? Подскажите пожалуйста!