Индикаторное табло на светодиодных матрицах

Посмотрел топик
TC15-11 — матрица светодиодная 8х8

Уж очень красиво выглядят светодиодные матрицы. Захотелось что-то сделать подобное. И конечно же на Arduino.
Лежали без дела уже 2 месяца две матрицы — одноцветных зеленых 8х8 (FYM-23881BUG-11)

Сначала решил создать табло с разными «анимациями».
Анимаций несколько, каждая состоит из нескольких кадров, меняющихся через какое-то время.Анимации можно выбирать кнопкой, кроме того управляем движением самой анимации по осям X и Y с помощью джойстика или клавиатуры.

Джойстик пока в пути, поэтому собрал 6 кнопочную аналоговую клавиатуру, которая подключается к аналоговому входу A5.
green кнопки — влево, вверх, вниз, вправо
white, red — служебные





Для управления двумя матрицами (128 светодиодов) будем использовать три выхода Arduino(8, 11,13)и 4 микросхемы 74HC595.

Схема подключения

Данные хранятся в массиве 8x24 байт, где верхние и нижние 4 байта служебные (будут использоваться в игре Тетрис), а средние 16 байт и есть данные для двух матриц. Обновление информации происходит 1 раз в 1000 мкс.
Для реализации динамической индикации, на выходах 1 и 3 микросхем будет выбираться один из столбцов, подаваемых на выходы 2 и 4 данных. При это мелькания нет. Управление светодиодов использует внутренний обработчик прерываний. Использование обработчика прерываний позволяет нам обновлять матрицу вне основного цикла программы, как бы в параллельном процессе. Для работы с прерываниями будем использовать Arduino-библиотеку Timer2.


На экран выводится информация из массива displayArray (байты 4-19).
Схема расположения диодов в матрицах очень запутана, в результате строки одной матрицы напаял в противоположном порядке,
поэтому дополнительная функция для исправления correctError().


// подключение SPI библиотеки (11,13,8  - MOSI, SCK, SS)
#include <SPI.h>
const int displayPin = 8;
// библиотека для прерываний по таймеру
#include <MsTimer2.h> 
byte displayArray[24] = {
0,0,0,0,
170,85,170,85,170,85,170,85,170,85,170,85,170,85,170,85,
0,0,0,0
};
void setup() {
  // инициализация SPI:
  pinMode (displayPin, OUTPUT);
  SPI.begin(); 
  // запуск прерывания по таймеру
  MsTimer2::set(2, showDisplay);  
  MsTimer2::start(); 
  // очистка дисплея
  clearDisplay()
}
void loop() {;}
// обработка прырывания по таймеру
// динамическая индикация
void showDisplay() {
  for(byte i=0;i<8;i++)
   {
  // SS=0 – защелкнуть выводы
   digitalWrite(displayPin,LOW);
   // 4,3
   // из-за ошибки пайки 
   byte bhigh=displayArray[(i+OFFSET_DISPLAY1.offsetY+8+16)%16+4];
   bhigh=circleShift(bhigh,OFFSET_DISPLAY1.offsetX);
           
   SPI.transfer(B00000001<<i);
   // 2,1
   byte blow=255-displayArray[(i+OFFSET_DISPLAY1.offsetY+16)%16+4];
   blow=circleShift(blow,OFFSET_DISPLAY1.offsetX);
   SPI.transfer(blow);
   SPI.transfer(B00000001<<i);
   //  SS=1 – вывести данные на выводы 74HC595
   digitalWrite(displayPin,HIGH); 
   // 1000 мкс
   delayMicroseconds(1000); 
   }
}
// коррекция ошибки пайки
byte correctError(byte a)
 {
 int arr[]={7,6,5,4,3,2,1,0};
 byte correct=B00000000;
 for(int i=0;i<8;i++)
   bitWrite(correct,i,bitRead(a,arr[i]));
  return correct; 
  }
// циклический сдвиг байта
byte circleShift(byte var,int offset)
 {
 byte varnew;
 for(int i=0;i<8;i++)
   bitWrite(varnew,i,bitRead(var,(i+offset+8)%8));
 return varnew;  
 } 
// очистка дисплея
void clearDisplay() {
  for(int i=0;i<sizeof(displayArray);i++)
    displayArray[i]=0;
}


Для хранения кадров всех анимаций, длительностей кадров и указателей смещения анимаций в общем массиве кадров создадим массивы значений arrdisplay1[][], arrtimes1[], startpozdisplay1[]. Данные для текущего кадра текущей анимации загружаются в массив displayArray(буфер экрана). Для хранения статусов кнопок и джойстика создадим структуру KEYS. Кнопки и джойстик будут служить для задания направления движения анимаций по осям дисплея. Для хранения текущих смещений показываемой картинки от позиции начального кадра и текущего направления движения создадим структуру OFFSET_DISPLAY.


struct KEYS    // структура для хранения статусов клавиш
{
  int button;      // нажатая кнопка
  int joystickX;      // x
  int joystickY;      // y
  int joystickZ;      // z
  long millisbutton[7];      // millis для button 
  long millis[7];      // millis для joystick 
};
struct OFFSET_DISPLAY    // структура для хранения смещений экранов
{
  int offsetX;      // x
  int offsetY;      // y
  int deltaX;      // dX
  int deltaY;      // dy
};
KEYS KEYS1={0,0,0,0,{0,0,0,0,0,0,0},0};
OFFSET_DISPLAY OFFSET_DISPLAY1={0,0,0,0};
// список обоев,задержек 4 анимации
byte arrdisplay1[19][16]={
   {192,192,48,48,12,12,3,3,192,192,48,48,12,12,3,3},
   {64,128,16,32,4,8,1,2,64,128,16,32,4,8,1,2},
   {128,64,32,16,8,4,2,1,128,64,32,16,8,4,2,1},
   
   {34,85,34,0,68,170,68,0,34,85,34,0,68,170,68,0},
   {0,120,0,0,68,68,68,0,0,120,0,0,68,68,68,0},
   {34,34,34,0,0,239,0,0,34,34,34,0,0,239,0,0},
 
   {24,24,24,24,48,24,48,48,24,24,48,48,24,24,24,48},
   {24,24,24,48,24,48,48,24,24,48,48,24,24,24,48,24},   
   {24,24,48,24,48,48,24,24,48,48,24,24,24,48,24,24},
   {24,48,24,48,48,24,24,48,48,24,24,24,48,24,24,24},
   {48,24,48,48,24,24,48,48,24,24,24,48,24,24,24,24},
   {24,48,48,24,24,48,48,24,24,24,48,24,24,24,24,48},

   {255,129,129,129,129,129,129,255,255,129,129,129,129,129,129,255},
   {255,255,195,195,195,195,255,255,255,255,195,195,195,195,255,255},
   {255,255,255,231,231,255,255,255,255,255,255,231,231,255,255,255},
   {0,0,0,24,24,0,0,0,0,0,0,24,24,0,0,0},
   {0,0,0,231,231,0,0,0,0,0,0,231,231,0,0,0},
   {0,0,195,195,195,195,0,0,0,0,195,195,195,195,0,0},
   {0,129,129,129,129,129,129,0,0,129,129,129,129,129,129,0}

 };
long arrtimes1[19]={200,200,200,100,100,100,50,50,50,50,50,50,80,80,80,150,80,80,80};
byte startpozdisplay1[]={0,3,6,12,19};




Предварительно, необходимо откалибровать клавиатуру. Если не нажата ни одна из кнопок, на вход A5 подается напряжение 5В через резистор 4,7 кОм. Будем выводить в последовательный порт значения, считываемые на входе A5 при нажатии на разные кнопки.


void loop() {
  int valbutton;
  game1();
  valbutton=analogRead(keyboardPin);
  Serial.println(valbutton); 
  if(valbutton<1000)
    {
    buttonClick2(buttonClick1(valbutton));
    }
}
// обработка нажатия кнопки
int buttonClick1(int val)
 {
  if(val>650) {KEYS1.button=1;return 1;}  
  if(val>600) {KEYS1.button=2;return 2;}  
  if(val>530) {KEYS1.button=3;return 3;}  
  if(val>450) {KEYS1.button=4;return 4;}  
  if(val>300) {KEYS1.button=5;return 5;}  
  if(val>200) {KEYS1.button=6;return 6;}  
  return 0;  
  }
// действия по нажатии кнопок
void buttonClick2(int val)
 {
   if(millis()-KEYS1.millisbutton[val]<100)
     return;
   KEYS1.millisbutton[val]=millis();
   KEYS1.button=val;
   switch(val) {
    case 1: OFFSET_DISPLAY1.deltaX=1;OFFSET_DISPLAY1.deltaY=0;
        FIGURA1.dX=FIGURA1.dX-1;                                            
        // Serial.println("left");
        break; 
    case 2: 
        OFFSET_DISPLAY1.deltaX=0;OFFSET_DISPLAY1.deltaY=-1;  // game1
        //Serial.println("up");
       break; 
    case 3: OFFSET_DISPLAY1.deltaX=0;OFFSET_DISPLAY1.deltaY=1;
        //Serial.println("down");
       break; 
    case 4: OFFSET_DISPLAY1.deltaX=-1;OFFSET_DISPLAY1.deltaY=0;
        FIGURA1.dX=FIGURA1.dX+1;                                         
        // Serial.println("right");
        break; 
    case 5: OFFSET_DISPLAY1.deltaX=0;OFFSET_DISPLAY1.deltaY=0;
        // переключение на другую программу
        tekpoz1=(tekpoz1+1)%(sizeof(startpozdisplay1)-1);
        tekindex1=startpozdisplay1[tekpoz1];
        speed2=max(speed2-25,5);
        //Serial.println("white");
       break; 
    case 6: OFFSET_DISPLAY1.deltaX=0;OFFSET_DISPLAY1.deltaY=0;
        //Serial.println("red");
        break; 
    default: 
        break; 
   }   
 }



В основном цикле программы loop() запускаем процедуру game1(), которая через промежуток длительности кадра arrtimes1[tekindex1], загружает в буфер экрана arrdisplay1 новые данные. Переключение на следующую анимацию происходит при нажатии на кнопку white.

 // режим1 - обои
void game1() {
  if((millis()-endmillis11)>arrtimes1[tekindex1])
   {
    OFFSET_DISPLAY1.offsetX=(OFFSET_DISPLAY1.offsetX+ 
           OFFSET_DISPLAY1.deltaX)%8;
    OFFSET_DISPLAY1.offsetY=(OFFSET_DISPLAY1.offsetY+   
           OFFSET_DISPLAY1.deltaY)%8;
    // установка нового кадра
    changeDisplay1(); 
    endmillis11=millis();
   }
}
// новый экран (кадр)
void changeDisplay1() {
  tekindex1++; 
  if(tekindex1==startpozdisplay1[tekpoz1+1])  
    tekindex1=startpozdisplay1[tekpoz1];
  for(int i=4;i<sizeof(displayArray)-4;i++)
    displayArray[i]=arrdisplay1[tekindex1][i-4];
}





Видео получилось плохого качества. И анимации сделать надо нормальные.
К тому же пропал контакт на один столбец.
Но это уже позже. А пока добавим игру Тетрис…

Комментарии (0)

RSS свернуть / развернуть

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.