Динамическая индикация для самодельной LED матрицы. Как лучше реализовать?

prosto_andriy
Offline
Зарегистрирован: 09.12.2012

Спаял такую вот матрицу из сорока светодиодов (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);
 }

 

tsostik
Offline
Зарегистрирован: 28.02.2013

В принципе, да, вся динамическая индикация так и делается.

Только надо отделять друг от друга две задачи:

1)Вывод готового изображения на экран. В идеале, эту задачу можно повесить на прерывание от таймера, которое вызывается один раз в 2 мс и отрисовывает очередную строчку. Это даст частоту одновления в 100 гц - не сильно нагружает МК и одновременно хорошо смотрится для глаза.

2)Собственно подготовка изображения. Выделяете "видеопамять" (5 байт, по одному на строку идеально вам подходят) и в основном цикле ее заполняете необходимой картинкой. В частности, для того, чтобы текст бежал нужно просто применить операцию циклического сдвига к каждому байту, повторяя ее раз в некоторое время типа одной секунды.

Только учтите, что операция digitalWrite занимает достаточно долгое время, поэтому лучше писать в порты напрямую, иначе велик риск все процессорное время убить на отрисовку.

prosto_andriy
Offline
Зарегистрирован: 09.12.2012

tsostik пишет:

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

Я подключил эту LED матрицу к Atmega8. Как в этой микросхеме можно выделить "видеопамять"? Подскажите пожалуйста, откуда её выделять: из FLASH? из RAM? из EEPROM?

tsostik пишет:

Только учтите, что операция digitalWrite занимает достаточно долгое время, поэтому лучше писать в порты напрямую, иначе велик риск все процессорное время убить на отрисовку.

А как можно "писать в порты напрямую"? Подскажите пожалуйста? Я думал, что проще функции digitalWrite ничего вообще нет! Ведь она только выдает 0 или 1 на определённом выводе. Или я ошибаюсь??? Подскажите тогда пожалуйста, какие функции мне лучше использовать для формирования и вывода изображения.

P.S. Заранее спасибо всем ответившим!

 

tsostik
Offline
Зарегистрирован: 28.02.2013

prosto_andriy пишет:

Я подключил эту LED матрицу к Atmega8. Как в этой микросхеме можно выделить "видеопамять"? Подскажите пожалуйста, откуда её выделять: из FLASH? из RAM? из EEPROM?

 Специальной видеопамяти никакой, конечно нет.

Просто в программе объявить

byte videoMem[5];

После чего использовать videoMem[0] для содержимого первой строки, videMem[1] - для второй и тд.

prosto_andriy пишет:

А как можно "писать в порты напрямую"? Подскажите пожалуйста? Я думал, что проще функции digitalWrite ничего вообще нет! Ведь она только выдает 0 или 1 на определённом выводе. Или я ошибаюсь???

http://arduino.ru/Tutorial/Upravlenie_portami_cherez_registry

Функции, конечно, простые, но кроме написания числа в порт им еще надо

1)Преобразовать номар порта в понимании ардуино в регистр и номер бита в понимании атмеги.

2)Проверить, нет ли на "порту" ШИМа

3а)Отключить его если что.

4)Проверить, что мы хотим - писать или читать.

В результате там, где процессору нужно реально один  или четыре тактат (в зависимости от того, надо ли нам сохранить остальное содержимое регистра), digitalWrite тратит по самым скромным подсчетам минимум в десять раз медленне.

prosto_andriy пишет:

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

Для формирования - смотря что вы хотите, но при таких объемах проще "в голове прикинуть"

Для вывода - http://arduino.ru/Reference/Library/SPI

Просто в цикле выводить по одному байту из videoMem[i] в SPI

 
leshak
Offline
Зарегистрирован: 29.09.2011

prosto_andriy пишет:

Я подключил эту LED матрицу к Atmega8. Как в этой микросхеме можно выделить "видеопамять"? Подскажите пожалуйста, откуда её выделять: из FLASH? из RAM? из EEPROM?

Можно в любой. Но наиболее просто/естественно/быстродействие - конечно в RAM.

А вообще, похоже вы себе тут представили что-то намного более страшно-сложное чем есть на самом деле.

Никакой особой "видеопамяти" в Atmega8 естественно нет. Выделение - это вы просто объявляете массив. И у вас есть две части програмы. Одна занимаете тем что выводит содержимое этого массива на диоды. А другая - тем что меняет содержимое этого массива когда нужно (по нажатию кнопок, по датчикам и т.п.)
Так что, в данном случае "видео-память" - это скорее термин который существует только в голове, а не железе. 

Вообщем

byte videoMemory[5];

это и есть "выделили видео-память".

prosto_andriy пишет:

А как можно "писать в порты напрямую"? Подскажите пожалуйста? Я думал, что проще функции digitalWrite ничего вообще нет! Ведь она только выдает 0 или 1 на определённом выводе. Или я ошибаюсь???

И да и нет. Вы не ошиблись в том что делает digitalWrite "с точки зрения пользователя", но ошиблись в том что поставили знак равенства между "простое поведение,просто пользоватся" и "просто устроено внутри".

Где читать? В шапке сайта, есть ссылка Программирование, там есть раздел "Базовые и полезные знания, необходимые для успешного программирования под платформу Arduino". В котором, в свою очередь Прямое управления выходами через регистры микроконтроллера Atmega

И посмотрите какие-там рядом статьи /примеры есть. Еще парочку вам пригодятся - про сдвиговые, к примеру.. Этим вы избежите будущих посыланий в поиск и документацию.

Но... вы все равно попробуйте поиском тоже попользоватся. И сдвиговые (по имени микрухи и по словам "множим выходы") и матрицы диодов (слова "LED матрица", "динамическая индикация") - не раз обсуждались. Покопайтесь - возможно многие будущие вопросы сами отпадут :)

А еще, по поводу прямой записи в регистры можете почитать AVR. Учебный курс. Устройство и работа портов ввода-вывода ... Тоже полезно для лучшего понимая всей этой кухни.

И возвращаясь к digitalWrite, после прочтения всего что выше, как видите включение ноги в нужно состояние требует выставить правильный бит, в правильном порту. Но... на разных камнях эти порты/биты разные. А скетчи - универсальные. digitalWrite(13,HIGHT) одинаково отработает и на уно и на меге. Именно за счет "интелектуальности" фунции digitalWrite. Которая, в зависимости от платы, сама решит какой порт/бит нужно выставить. Но за эту универсально/разумность/легкую преносимость - нужно платить. Тактами процессора и невозможностью поменять состояние сразу нескольких ног одновременно.

Поэтому когда на важна скорость - приходится откадываться от digitalWrite и самому смотреть в даташит и пин-маппинг что чему соотвествует. Выигрываем в скорости, но теряем в универсальности (при переходе на другой камень - нужно будет править скетч) и легкости читания кода.

 

 

msng
Offline
Зарегистрирован: 07.06.2012

вот такая статья оч понравилась, все там доходчиво и ясно, правда только с бОльшей матрицей, но принцип один...

prosto_andriy
Offline
Зарегистрирован: 09.12.2012

Мне кажеться, что оптимальный вариант - использование двух массивов битов: один - для строк, второй - для столбцов.

Только вот, как правильно объединить их в одно целое для вывода определенного символа на матрицу?

Ведь один байт (для выбора нужных столбцов) очевидно должен передаваться на регистр по SPI, второй байт (для выбора нужной строки) будет неполным! И передаваться он должен напрямую!

В Atmega8 для выбора нужной строки задействованы такие выводы 

DDRD = DDRD | B01111100;

Как использовать операцию сдвига, чтобы он происходил не до конца, то есть не выходил за пределы задействованных выводов?

Может кто-нибудь подскажет?

 

prosto_andriy
Offline
Зарегистрирован: 09.12.2012

msng пишет:

вот такая статья оч понравилась, все там доходчиво и ясно, правда только с бОльшей матрицей, но принцип один...

Спасибо за ссылку! Статья действительно хорошая. Но если действовать по тому же принципу,  придётся использовать два регистра сдвига (один для столбцов, другой - для строк).

Я же хочу довести до ума  пример, где нужные светодиоды в столбцах матрицы выбираються через регистр сдвига, а нужная строка - появлением логической единицы на соответствующем выводе 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. Но вот как "запихивать" в него нужные значения? Нужно создавать какую-то таблицу знакогенератора? Подскажите пожалуйста!!!

 

msng
Offline
Зарегистрирован: 07.06.2012

вот такое я писАл

"занкогенеротор" так сказать в строках 4 и 5

ну а запихивать в него типа

matrix[1]=5;

matrix[2]=8;

ну и т.д. это в моем скетче...

Вам в регистр нужно отправлять только одно слово (1 или 2 или 4 или 8 или 16 и т.д. т.е. перебирайте столбцы в двоичном коде), а на строки с выводов дуньки, то есть правильно мыслите... т.е. создаете цикл в котором поочереди перебираются столбцы и отправляются в регистр, ну и во время выбора первого столбца отправляйте на выводы дуньки из массива знакогенератора первое слово, во время выбора второго столбца - второе слово знакогенератора... ну и т.д...

 

 

prosto_andriy
Offline
Зарегистрирован: 09.12.2012

Написал две функции  - одну для вывыда столбцов, другую - для строк. Код стал выгледеть так:

#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...

Может быть это из-за того, что я "запихнул" одну функцию в другую??? Подскажите пожалуйста, может кто-нибудь знает???

 

step962
Offline
Зарегистрирован: 23.05.2011

Ради интереса - посмотрите на строку 44 и попробуйте найти различия со строкой 29.

Их там два. Компилятор оба этих отличия находит, теперь дело за вами.

Далее - что должны символизировать фигурные скобки в строках 28 (открывающая) и 30 (закрывающая)?

И почему оператор в строке 28 не завершен, как и полагается, символом ";"? Чего вы хотели этим добиться? Конструкция настолько необычная (наверняка, кроме ругательства на слишком малое число аргументов у вас далее еще есть сообщение что-то вроде "error: expected `;' before '{' token"), что без ваших объяснений трудно понять ее, ну и, соответственно, порекомендовать правильный вариант исправления (чтобы не только синтаксически верно было, но и задачу решало).

 

prosto_andriy
Offline
Зарегистрирован: 09.12.2012

Спасибо за то, что указали на ошибки!

Я поправил код! Теперь он выглядит так:

#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.: Я очень детально прокомментировал свой код, пожалуйста, кому не лень, посмотрите, что в нём может быть неправильно!

kisoft
kisoft аватар
Offline
Зарегистрирован: 13.11.2012

Одно непонятно, зачем смешивать использование DDRD, digitalWrite и т.п. Это называется искать проблемы

prosto_andriy
Offline
Зарегистрирован: 09.12.2012

kisoft пишет:
Одно непонятно, зачем смешивать использование DDRD, digitalWrite и т.п. Это называется искать проблемы

digitalWrite используеться для трех выводов порта С. Разве он имеет какое-то отношения к порту D, для которого я использовал прямую запись в порт

PORTD=rowsArray[rowsCounter]; // выбираем из массива, какую строку включить

Разве применение такого способа в паре с 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); //кратковременная пауза
 // и т.д. загружаем в регистр значения, включаем нужную строку...

}

 

__Alexander
Offline
Зарегистрирован: 24.10.2012

prosto_andriy пишет:

Может быть кто-то из специалистов подскажет, что я написал в коде неправильно?

void loop() {

stolbetsLED; //загружаем значения в регистр для отображения столбца

strokaLED; // вкл. соответствующую строку

delay(20); //кратковременная пауза // и т.д. загружаем в регистр значения, включаем нужную строку... }

Всё не смотрел, но функции надо вызывать с аргументами, если их нет, то скобка всё равно должна быть.

stolbetsLED(); - вот так.

и ваще, вы не правильно написали функции...

void stolbetsLED(byte collsArray[], byte collsCounter){

так вот этот аргумент collsArray совсем не тот, что вы описали выше, это заново созданный указатель, не имеющий отношения к предыдущему.

 

prosto_andriy
Offline
Зарегистрирован: 09.12.2012

__Alexander пишет:

...и ваще, вы не правильно написали функции...

void stolbetsLED(byte collsArray[], byte collsCounter){

так вот этот аргумент collsArray совсем не тот, что вы описали выше, это заново созданный указатель, не имеющий отношения к предыдущему.

А куда же тогда он указывает? И как обратиться к ранее созданной переменной (или массиву)? Или это невозможно? Подскажите пожалуйста!