5 июня 2014 г.

«Бегущая строка» — динамическая индикация на примере светодиодной матрицы RL-M2388 и Arduino Mega

(c)
Данная статья не претендует на толкование как единственно возможного метода отображения и прокрутки информации на светодиодной матрице. Я с удовольствием выслушаю ваши замечания и предложения по улучшению метода/функций. Данная статья – изложение личного опыта и описание результата, которого я добился.

Немного теории


В данной светодиодной матрице находится 64 красных светодиода. Если выводить контакты каждого светодиода отдельно, то понадобится 64 контакта на корпусе матрицы и микроконтроллер с 65 цифровыми портами ввода/вывода. Это нецелесообразно и невыгодно. Поэтому на заводе-изготовителе светодиоды объединяют в матрицы различных размеров (в нашем случае 8х8), то есть в 8 групп по строкам и столбцам следующим образом: 
image

В таком случае нам понадобится лишь 16 цифровых вводов/выводов. Например, чтобы зажечь светодиод в левом верхнем углу, нам нужно на pin13 (см. картинку) подать лог 1, а на pin9 лог 0. Такой способ отображения называется статическая индикация. 

«Хорошо, а если нам, например, нужно зажечь несколько светодиодов в разных позициях матрицы, а все остальные оставить выключенными?» — спросите Вы. При статической индикации это не представляется возможным. Для этого нужно использовать динамическую индикацию.


Динамическая индикация

Быстро мелькающий объект кажется человеческому глазу постоянно светящимся. Это свойство человеческого глаза – инертность. Как вы догадались, именно на этом свойстве и основан метод вывода информации в светодиодной матрице. Например, чтобы вывести на «экран» некий символ, нужно последовательно, проходя все «пиксели» матрицы с высокой скоростью, включать светодиод в нужном месте. 

Условия, которые необходимо соблюдать при программировании матриц динамической индикации:
1. Длительность отображения каждого столбца/строки («пикселя» в моем случае) постоянна, одинакова для всех столбцов/строк («пикселей» в моем случае).
2. Частота смены столбцов/строк («пикселей» в моем случае) не меняется.

Принцип действия прошивки микроконтроллера

Признаюсь, когда у меня появилась отладочная плата на МК Atmega1280 и светодиодная матрица, я и понятия не имел что такое динамическая индикация и для чего она нужна. До понимания необходимости ее использования я дошел методом проб и методом «тыка».

Принцип действия прошивки:

1. Вывод символа на экран

Давайте представим, что наша светодиодная матрица – это двухмерный массив размерностью I на J, совпадающей с размерностью нашей матрицы. В нашем случае это 8 на 8 пикселей. Итак, есть двухмерный массив типа boolean. К примеру: 

boolean A[8][8] =
{0,0,1,1,1,1,0,0,
 0,1,0,0,0,0,1,0,  
 0,1,0,0,0,0,1,0,  
 0,1,0,0,0,0,1,0,  
 0,1,1,1,1,1,1,0,  
 0,1,0,0,0,0,1,0,  
 0,1,0,0,0,0,1,0,  
 0,1,0,0,0,0,1,0}  


В цикле мы проверяем, если элемент массива A[i][j]=1, тогда включаем светодиод на матрице, находящийся в позиции (I;J), делаем паузу на отображения светодиода и выключаем светодиод в позиции (I;J). В результате работы программы по такому алгоритму на экране матрицы выведется символ «А» (в двумерном массиве именно этот символ отображен). Назовем этот алгоритм «Вывод на экран». Итак, с выводом информации на экран с помощью динамической индикации разобрались.

2. Прокрутка информации на экране

Конечно, классно выводить на экран неподвижную информацию, но было бы интереснее «оживить» ее – заставить передвигаться. Рассмотрим алгоритм перемещения информации:

В бесконечном цикле выполняем:
1. Вызываем алгоритм «Вывод на экран».
2. Берем 1й столбец матрицы А и записываем его в буфер (двухмерный массив BUFFER[8][1]). 
3. Записываем содержимое матрицы с позиции A[i][j+1] в позицию A[i][j]. То есть мы по сути «сдвинули» матрицу влево на один столбец.
4. Записываем в последний столбец матрицы А (в свободный, так как мы сдвинули матрицу влево на один столбец) содержание буфера BUFFER. Назовем этот алгоритм«Прокрутка информации».

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

Подключение матрицы к отладочной плате

Каждый из 16ти выводов матрицы пронумерован. В соответствии этим номерам были подпаяны и выведены 8 контактов, отвечающих за строки, и 8 контактов, отвечающих за столбцы. У меня, например, матрица подключена так:

Выводы, отвечающие за строки 23,25,27,29,31,33,35,37 – это номера выводов ножек микроконтроллера;
Выводы, отвечающие за столбцы 39,41,43,45,47,49,51,53 — это номера выводов ножек микроконтроллера.

Программирование

Программирование и прошивка микроконтроллера производится в среде разработки Arduino IDE.

Что необходимо знать для понимания кода программы, написанного ниже:

Функция pinMode()
Устанавливает режим работы заданного вход/выхода(pin) как входа или как выхода.
pinMode(pin, mode), где pin: номер вход/выхода(pin), который Вы хотите установить; mode: режим одно из двух значение — INPUT или OUTPUT, устанавливает на вход или выход соответственно.

Функция digitalWrite()
Подает HIGH или LOW значение на цифровой вход/выход (pin).
digitalWrite(pin, value), где pin: номер вход/выхода(pin); value: значение HIGH или LOW

Функция delayMicroseconds()
Останавливает выполнение программы на заданное в параметре количество микросекунд (1 000 000 микросекунд в 1 секунде). 
delayMicroseconds(us), где us: количество микросекунд, на которое приостанавливается выполнение программы. (unsigned int)

Структура любой программы (скетча, так как в среде разработки ардуино программы называются именно так) имеет следующий вид:

void setup()
{
…
}

void loop()
{
  …
}


В функции setup() производятся настройки портов ввода/вывода МК, настройка подключенных устройств к МК, периферии МК, а так же выполняется все то, что нужно выполнить ОДИН раз.
В функции loop() пишется тело программы, которое будет выполняться циклически, пока микроконтроллер включен. 

Выполним настройку выводов микроконтроллера. Состояний портов ввода/вывода существует два вида: настроен на вход либо на выход. В нашем случае нужно настроить на выход.

const int row[8]={23,25,27,29,31,33,35,37}; // Помните 8 выводов, отвечающих за строки и
const int col[8]={39,41,43,45,47,49,51,53}; // столбцы? А вот и они. 

void setup()
{
     for (int i=0; i<8; i++)
{
     pinMode(row[i],OUTPUT); // В цикле все выводы переводим в режим «на вывод»
     pinMode(col[i],OUTPUT); // Выключаем все светодиоды
}
     for (int i=0; i<8; i++)
         {
             digitalWrite(row[i],HIGH);
         }
}


Давайте теперь реализуем алгоритмы, которые я описал выше. Итак, мы имеем двухмерный массив:

boolean A[8][8] = 
{0,0,1,1,1,1,0,0,
0,1,0,0,0,0,1,0,  
0,1,0,0,0,0,1,0,  
0,1,0,0,0,0,1,0,  
0,1,1,1,1,1,1,0,  
0,1,0,0,0,0,1,0,  
0,1,0,0,0,0,1,0,  
0,1,0,0,0,0,1,0} 


Напишем функцию, которая реализует алгоритм «Вывод на экран».

int v=3; //Скорость прокручивания информации. То есть по сути это количество итераций цикла отрисовки информации в матрице
int dms=400; //Скорость обновления «пикселей». То есть время, когда светодиод находится во включенном состоянии. Чем оно больше, тем более заметнее глазу будет мерцание изображения
void paint(boolean screen[8][8], int v) //В качестве параметров передаем матрицу и параметр v  
{  
int i, j;
for (int c=0; c <v; c ++) // Тот самый цикл задержки отрисовки информации 
  {
    for (i=0; i<8; i++)
      for (j=0; j<8; j++)
        {
          if (screen[i][j]= =1) // Если элемент массива = 1
            {
              digitalWrite(row[i], LOW); 
              digitalWrite(col[j], HIGH); 
              delayMicroseconds(dms); 
        digitalWrite(row[i], HIGH); 
              digitalWrite(col[j], LOW); //  То включить светодиод                 в проверяемой позиции, задержать включенным, выключить светодиод.
            }
            else 
               {
                  digitalWrite(row[i], HIGH); 
                  digitalWrite(col[j], LOW); 
                  delayMicroseconds(dms);
                  digitalWrite(row[i], HIGH); 
                  digitalWrite(col[j], LOW);
              } // Иначе выключить светодиод в проверяемой позиции, задержать включенным, обновить экран. (даже если светодиод и был выключенным в этой позиции, то все равно его нужно «выключить» для того, чтобы проверка в цикле и действия проходили с одинаковой задержкой. Если в позиции 1 – то выполняются действия, если в позиции 0 – все равно должно что-то выполнится либо выполнится задержка, сопоставимая с задержкой выполнения включения светодиода)
        } 
    }
}


Напишем функцию, которая реализует алгоритм «Прокрутка информации».

void scroll(boolean screen[8][8]) // В качестве параметра передаем наш двухмерный массив   
{
boolean buf[8][1]; 
  for (int i=0; i<8; i++)
  {
    buf[i][0]=screen[i][0]; // Считали 1ю колонку в буфер
  }
  for (int i=0; i<8; i++)
    for (int j=0; j<8; j++)
      {
        screen[i][j]=screen[i][j+1]; // Сдвинули матрицу на один столбец влево
      }
  for (int i=0; i<8; i++)
  {
    screen[i][8-1]=buf[i][0]; // Записали содержимое буфера (первую колонку) в конец матрицы
  }
}


Теперь приведу полный код программы:

const int row[8]={23,25,27,29,31,33,35,37};
const int col[8]={39,41,43,45,47,49,51,53};
int v=3; 
int dms=400; 

boolean A[8][8] = 
{0,0,1,1,1,1,0,0,
0,1,0,0,0,0,1,0,  
0,1,0,0,0,0,1,0,  
0,1,0,0,0,0,1,0,  
0,1,1,1,1,1,1,0,  
0,1,0,0,0,0,1,0,  
0,1,0,0,0,0,1,0,  
0,1,0,0,0,0,1,0};  

void setup()
{
  for (int i=0; i<8; i++)
    {
      pinMode(row[i],OUTPUT);
      pinMode(col[i],OUTPUT);
    }
  for (int i=0; i<8; i++)
    {
      digitalWrite(row[i],HIGH); 
    }
}

void loop()
{
    paint(A, v);
    scroll(A);
}

void paint(boolean screen[8][8], int v) 
{  
int i, j;
for (int c=0; c <v; c ++) 
  {
    for (i=0; i<8; i++)
      for (j=0; j<8; j++)
        {
          if (screen[i][j]==1)            
           {
              digitalWrite(row[i], LOW); 
              digitalWrite(col[j], HIGH); 
              delayMicroseconds(dms); 
        digitalWrite(row[i], HIGH); 
              digitalWrite(col[j], LOW); 
           }
            else 
             {
                 digitalWrite(row[i], HIGH); 
                 digitalWrite(col[j], LOW); 
                 delayMicroseconds(dms); 
                 digitalWrite(row[i], HIGH); 
                 digitalWrite(col[j], LOW);
            } 
        } 
    }
}

void scroll(boolean screen[8][8]) 
{
boolean buf[8][1]; 
  for (int i=0; i<8; i++)
  {
    buf[i][0]=screen[i][0]; 
  }
  for (int i=0; i<8; i++)
    for (int j=0; j<8; j++)
      {
        screen[i][j]=screen[i][j+1]; 
      }
  for (int i=0; i<8; i++)
  {
    screen[i][8-1]=buf[i][0]; 
  }
}


Вот и все. Заливайте эту программу в свой микроконтроллер и наблюдайте, как буква «А» будет «бежать» влево. Добавлю, что массивом размерностью 8 на 8 элементов ограничиваться не стоит. Например, я использовал массив размером 8 на 86. Написал в нем целую фразу, как видно на этом видео.

Комментариев нет:

Отправить комментарий