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