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


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

Добавить комментарий

Arduino

Что такое Arduino?
Зачем мне Arduino?
Начало работы с Arduino
Для начинающих ардуинщиков
Радиодетали (точка входа для начинающих ардуинщиков)
Первые шаги с Arduino

Разделы

  1. Преимуществ нет, за исключением читабельности: тип bool обычно имеет размер 1 байт, как и uint8_t. Думаю, компилятор в обоих случаях…

  2. Добрый день! Я недавно начал изучать программирование под STM32 и ваши уроки просто бесценны! Хотел узнать зачем использовать переменную типа…

3D-печать AI Android Arduino Bluetooth CraftDuino DIY IDE iRobot Kinect LEGO OpenCV Open Source Python Raspberry Pi RoboCraft ROS swarm ИК автоматизация андроид балансировать бионика версия видео военный датчик дрон интерфейс камера кибервесна манипулятор машинное обучение наше нейронная сеть подводный пылесос работа распознавание робот робототехника светодиод сервомашинка собака управление ходить шаг за шагом шаговый двигатель шилд юмор

OpenCV
Робототехника
Будущее за бионическими роботами?
Нейронная сеть - введение