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