POV-бегущая строка из 8 светодиодов

  • Платформы: Iskra Mini
  • Языки программирования: Arduino (C++)
  • Тэги: Персистенция, акселерометр, рисуем светодиодами в воздухе, обман зрения.

Что это?

При быстром вращении зажжённого факела, вместо нескольких раздельных его положений, мы видим светящийся огненный круг, а когда смотрим кино отчетливо видим плавно двигающуюся картинку, а не отдельные кадры. Этот эффект называется персистенция — способность человеческого глаза соединять быстро сменяющиеся изображения в одно. Мы расскажем как выводить текстовые сообщения и картинки в воздухе, опираясь на этот эффект .

Что нам понадобится?

Как собрать?

  1. Так как акселерометр общается с управляющей электроникой по протоколу I²C, а на плате Iskra Mini эти пины не выведены, сделаем это сами. Для этого возьмите штырьковые соединители, откусите от них два пина и припаяйте с обратной стороны платы Iskra Mini к пинам A4 (SDA) и A5 (SCL).
  2. Установите платформу Iskra Mini на макетную плату.
  3. Пины питания +5V и земли GND платформы Iskra Mini соедините с помощью проводов «папа-папа» с рельсами питания и земли макетной платы соответственно.
  4. Установите акселерометр на Breadbord и с помощью проводов подключите следующим образом:
    • Пины питания V и земли G акселерометра с помощью проводов «папа-папа» соедините с рельсами питания и земли макетной платы соответственно.
    • Сигнальные пины D и C акселерометра с помощью проводов «папа-мама» соедините с пинами A4 (SDA) и A5 (SCL) платы Iskra Mini.
  5. Возьмите 8 красных светодиодов и установите их в ряд на макетную плату.
  6. Далее с помощью резисторов 220 Ом соедините катоды светодиодов с рельсой земли макетной платы.
  7. В продолжение используя провода «папа-папа» соедините аноды светодиодов со 2 по 9 пин платы Iska Mini.
  8. Переверните макетную плату. Возьмите кабель питания от батарейки Крона и соедините одну часть с Кроной, а вторую — с гнездом питания 2,1 мм с клеммником. Далее подключите в клеммник два провода «папа-папа». Теперь аккуратно отклейте защитную плёнку от breadbord и закрепите всю собранную конструкцию на липкой части макетки.
  9. В заключение подключите выведенные от клеммника два провода к платформе Iskra Mini таким образом:
    • контакт + клеммника — на Vin платы Iskra Mini;
    • контакт клеммника — на GND платы Iskra Mini.

В итоге получилась схема, приведенная ниже.

Алгоритм

  • Сразу после подачи питания считываем направления и величину ускорения по оси Y.
    • Если ускорение превышает норму и направленно слева направо выводим буквы в обычном порядке.
    • Если ускорение превышает норму и направленно справа налево выводим буквы в обратно порядке зеркально отраженные.
  • Гасим все светодиоды и повторяем весь выше описанный цикл.

Исходный код

povdisplay.ino
// библиотека для работы I²C
#include <Wire.h>
 
// библиотека для работы с модулями IMU
#include <TroykaIMU.h>
 
// задержка между столбцами одного символа
// ширина символа
#define TIMER_COLUMS 3
 
// задержка между символами
// ширина между символами
#define TIMER_FRAMES 8
 
// количество светодиодов, высота символа
#define ROW  8
// количество столбцов в символе
#define COL  5
 
// массив пинов, к которым подключены светодиоды
int pins[] = {2, 3, 4, 5, 6, 7, 8, 9};
 
// создаём объект для работы с акселерометром
Accelerometer accel;
 
// переменная для хранения
// направления и величины ускорения по оси Y
float y = 0;
 
// переменная для хранения кол-во раз
// подряд положительных значений направленного ускорения
int y_pol = 0;
 
// переменная для хранения кол-во раз
// подряд отрицательных значений направленного ускорения
int y_neg = 0;
 
// двумерный массив символов
const unsigned char image[][COL] = {
  {0x00, 0x00, 0x00, 0x00, 0x00},  // space
  {0x00, 0xF6, 0xF6, 0x00, 0x00},  // !
  {0x00, 0xE0, 0x00, 0xE0, 0x00},  // "
  {0x28, 0xFE, 0x28, 0xFE, 0x28},  // #
  {0x00, 0x64, 0xD6, 0x54, 0x08},  // $
  {0xC2, 0xCC, 0x10, 0x26, 0xC6},  // %
  {0x4C, 0xB2, 0x92, 0x6C, 0x0A},  // &
  {0x00, 0x00, 0xE0, 0x00, 0x00},  // '
  {0x00, 0x38, 0x44, 0x38, 0x00},  // )
  {0x00, 0x82, 0x44, 0x38, 0x00},  // )
  {0x88, 0x50, 0xF8, 0x50, 0x88},  // *
  {0x08, 0x08, 0x3E, 0x08, 0x08},  // +
  {0x00, 0x00, 0x05, 0x06, 0x00},  // ,
  {0x08, 0x08, 0x08, 0x08, 0x08},  // -
  {0x00, 0x00, 0x06, 0x06, 0x00},  // .
  {0x02, 0x0C, 0x10, 0x60, 0x80},  // /
  {0x7C, 0x8A, 0x92, 0xA2, 0x7C},  // 0
  {0x00, 0x42, 0xFE, 0x02, 0x00},  // 1
  {0x42, 0x86, 0x8A, 0x92, 0x62},  // 2
  {0x44, 0x82, 0x92, 0x92, 0x6C},  // 3
  {0x10, 0x30, 0x50, 0xFE, 0x10},  // 4
  {0xE4, 0xA2, 0xA2, 0xA2, 0x9C},  // 5
  {0x3C, 0x52, 0x92, 0x92, 0x0C},  // 6
  {0x80, 0x86, 0x98, 0xE0, 0x80},  // 7
  {0x6C, 0x92, 0x92, 0x92, 0x6C},  // 8
  {0x60, 0x92, 0x92, 0x94, 0x78},  // 9
  {0x00, 0x00, 0x36, 0x36, 0x00},  // :
  {0x00, 0x00, 0x35, 0x36, 0x00},  // ;
  {0x10, 0x28, 0x44, 0x82, 0x00},  // <
  {0x28, 0x28, 0x28, 0x28, 0x28},  // =
  {0x00, 0x82, 0x44, 0x28, 0x10},  // >
  {0x40, 0x80, 0x8A, 0x90, 0x60},  // ?
  {0x7C, 0x82, 0xBA, 0xBA, 0x62},  // @
  {0x3E, 0x48, 0x88, 0x48, 0x3E},  // A
  {0xFE, 0x92, 0x92, 0x92, 0x6C},  // B
  {0x7C, 0x82, 0x82, 0x82, 0x44},  // C
  {0xFE, 0x82, 0x82, 0x82, 0x7C},  // D
  {0xFE, 0x92, 0x92, 0x92, 0x82},  // E
  {0xFE, 0x90, 0x90, 0x90, 0x80},  // F
  {0x7C, 0x82, 0x82, 0x8A, 0x4E},  // G
  {0xFE, 0x10, 0x10, 0x10, 0xFE},  // H
  {0x82, 0x82, 0xFE, 0x82, 0x82},  // I
  {0x84, 0x82, 0xFC, 0x80, 0x80},  // J
  {0xFE, 0x10, 0x28, 0x44, 0x82},  // K
  {0xFE, 0x02, 0x02, 0x02, 0x02},  // L
  {0xFE, 0x40, 0x20, 0x40, 0xFE},  // M
  {0xFE, 0x60, 0x10, 0x0C, 0xFE},  // N
  {0x7C, 0x82, 0x82, 0x82, 0x7C},  // O
  {0xFE, 0x90, 0x90, 0x90, 0x60},  // P
  {0x7C, 0x82, 0x82, 0x86, 0x7E},  // Q
  {0xFE, 0x90, 0x98, 0x94, 0x62},  // R
  {0x64, 0x92, 0x92, 0x92, 0x4C},  // S
  {0x80, 0x80, 0xFE, 0x80, 0x80},  // T
  {0xFC, 0x02, 0x02, 0x02, 0xFC},  // U
  {0xF8, 0x04, 0x02, 0x04, 0xF8},  // V
  {0xFC, 0x02, 0x0C, 0x02, 0xFC},  // W
  {0xC6, 0x28, 0x10, 0x28, 0xC6},  // X
  {0xC0, 0x20, 0x1E, 0x20, 0xC0},  // Y
  {0x86, 0x8A, 0x92, 0xA2, 0xC2},  // Z
  {0x00, 0x00, 0xFE, 0x82, 0x00},  // [
  {0x00, 0x00, 0x00, 0x00, 0x00},  // \
  {0x80, 0x60, 0x10, 0x0C, 0x02},  // ]
  {0x20, 0x40, 0x80, 0x40, 0x20},  // ^
  {0x01, 0x01, 0x01, 0x01, 0x01},  // _
  {0x80, 0x40, 0x20, 0x00, 0x00},  // `
  {0x04, 0x2A, 0x2A, 0x2A, 0x1E},  // a
  {0xFE, 0x12, 0x22, 0x22, 0x1C},  // b
  {0x1C, 0x22, 0x22, 0x22, 0x14},  // c
  {0x1C, 0x22, 0x22, 0x12, 0xFE},  // d
  {0x1C, 0x2A, 0x2A, 0x2A, 0x18},  // e
  {0x10, 0x7E, 0x90, 0x80, 0x40},  // f
  {0x18, 0x25, 0x25, 0x25, 0x1E},  // g
  {0xFE, 0x10, 0x10, 0x10, 0x0E},  // h
  {0x00, 0x12, 0x5E, 0x02, 0x00},  // i
  {0x02, 0x01, 0x01, 0x11, 0x5E},  // j
  {0xFE, 0x08, 0x08, 0x14, 0x22},  // k
  {0x00, 0x82, 0xFE, 0x02, 0x00},  // l
  {0x3E, 0x20, 0x1C, 0x20, 0x1E},  // m
  {0x3E, 0x20, 0x20, 0x20, 0x1E},  // n
  {0x1C, 0x22, 0x22, 0x22, 0x1C},  // o
  {0x3F, 0x24, 0x24, 0x24, 0x18},  // p
  {0x18, 0x24, 0x24, 0x3F, 0x01},  // q
  {0x3E, 0x10, 0x20, 0x20, 0x10},  // r
  {0x12, 0x2A, 0x2A, 0x2A, 0x04},  // s
  {0x00, 0x10, 0x3C, 0x12, 0x04},  // t
  {0x3C, 0x02, 0x02, 0x02, 0x3E},  // u
  {0x30, 0x0C, 0x02, 0x0C, 0x30},  // v
  {0x38, 0x06, 0x18, 0x06, 0x38},  // w
  {0x22, 0x14, 0x08, 0x14, 0x22},  // x
  {0x38, 0x05, 0x05, 0x05, 0x3E},  // y
  {0x22, 0x26, 0x2A, 0x32, 0x22},  // z
  {0x00, 0x10, 0x6C, 0x82, 0x82},  // {
  {0x00, 0x00, 0xFF, 0x00, 0x00},  // |
  {0x82, 0x82, 0x6C, 0x10, 0x00},  // }
  {0x08, 0x10, 0x18, 0x08, 0x10},  // ~
  {0x38, 0x7c, 0x3e, 0x7c, 0x38},  // Love 127
};
 
void setup()
{
  // инициализация акселерометра
  accel.begin();
  // устанавливаем чувствительность
  accel.setRange(RANGE_2G);
  // устанавливаем пины из массива pins[] в режим выхода
  for (int i = 0; i < ROW; i++) {
    pinMode(pins[i], OUTPUT);
  }
  // гасим все светодиоды
  ledsAllOff();
}
 
void loop()
{
  // считываем направления и величину ускорения по оси Y
  y = accel.readAY());
  // если ускорение превысило допустимое значение
  // и направлено с права налево
  if (y > 15) {
    y_pol++;
    if (y_pol == 60) {
      y_pol = 0;
      // так как ускорение направлено справа налево
      // выводим буквы в обратном порядке
      // функцией которая их зеркально отражает
      showRL('a');
      showRL('k');
      showRL('r');
      showRL('e');
      showRL('p');
      showRL('m');
      showRL('A');
      delay(10);
    }
    // если ускорение превысило допустимое значение
    // и направлено с лево на право
  } else if (y < - 15) {
    y_neg++;
    if (y_neg == 60) {
      y_neg = 0;
      // так как ускорение направлено слева направо
      // выводим буквы в обычном порядке
      showLR('A');
      showLR('m');
      showLR('p');
      showLR('e');
      showLR('r');
      showLR('k');
      showLR('a');
      delay(10);
    }
  }
}
 
// функция вывода символа слева направо
void showLR(unsigned char c)
{
  // перебор столбцов матрицы
  for (int i = 0; i < COL; i++) {
    // перебор строк матрицы (светодиодов)
    ledsSet(c, i);
    // задержка задающая ширину символа
    delay(TIMER_COLUMS);
  }
 
  // гасим все светодиоды
  ledsAllOff();
  // задержка задающая ширину между символами
  delay(TIMER_FRAMES);
}
 
// функция вывода символа справа налево
void showRL(unsigned char c)
{
  // перебор столбцов матрицы
  for (int i = COL - 1; i >= 0; i--) {
    // перебор строк матрицы (светодиодов)
    ledsSet(c, i);
    // задержка задающая ширину символа
    delay(TIMER_COLUMS);
  }
  // гасим все светодиоды
  ledsAllOff();
  // задержка задающая ширину между символами
  delay(TIMER_FRAMES);
}
 
// функция гасящая все светодиоды
void ledsAllOff()
{
  for (int i = 0; i < ROW; i++) {
    digitalWrite(pins[i], LOW);
  }
}
 
// функция включения светодиодов в текущем столбце
void ledsSet(unsigned char c, int i)
{
  for (int j = 0; j < ROW; j++) {
    digitalWrite(pins[j], bitRead(image[c-32][i], j));
  }
}

Демонстрация работы устройства

Что дальше?

В этот примере мы показали самое простое применение персистенции. Добавив механическую конструкцию, увеличив количество и разнообразив цвета светодиодов можно собрать более сложные и интереснее устройства. А если использовать RGB-светодиоды, можно собрать полноценный виртуальный дисплей в воздухе.