Посмотрел топик
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.

Схема подключения
Данные хранятся в массиве 8×24 байт, где верхние и нижние 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];" spi.transfer(blow);="" spi.transfer(b00000001<<i);="" ss="1" –="" вывести="" данные="" на="" выводы="" 74hc595="" digitalwrite(displaypin,high);="" 1000="" мкс="" delaymicroseconds(1000);="" }="" коррекция="" ошибки="" пайки="" correcterror(byte="" a)="" {="" int="" arr[]="{7,6,5,4,3,2,1,0};" correct="B00000000;" for(int="" i="0;i<8;i++)" bitwrite(correct,i,bitread(a,arr[i]));="" return="" correct;="" циклический="" сдвиг="" байта="" circleshift(byte="" var,int="" offset)="" varnew;="" bitwrite(varnew,i,bitread(var,(i+offset+8)%8));="" очистка="" дисплея="" void="" cleardisplay()="" displayarray[i]="0;" <="" pre="">
Для хранения кадров всех анимаций, длительностей кадров и указателей смещения анимаций в общем массиве кадров создадим массивы значений 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];" }="" <="" pre="">
https://www.youtube.com/watch?v=U7tYXTaQ2I
Видео получилось плохого качества. И анимации сделать надо нормальные.
К тому же пропал контакт на один столбец.
Но это уже позже. А пока добавим игру Тетрис …
</sizeof(displayarray)-4;i++)>
</i);>
