====== POV-бегущая строка из 8 светодиодов ====== {{ :projects:povdisplay:povdisplay_overvew.jpg?nolink&700 |}} * Платформы: Iskra Mini * Языки программирования: Arduino (C++) * Тэги: Персистенция, акселерометр, рисуем светодиодами в воздухе, обман зрения. ===== Что это? ===== При быстром вращении зажжённого факела, вместо нескольких раздельных его положений, мы видим светящийся огненный круг, а когда смотрим кино отчетливо видим плавно двигающуюся картинку, а не отдельные кадры. Этот эффект называется персистенция — способность человеческого глаза соединять быстро сменяющиеся изображения в одно. Мы расскажем как выводить текстовые сообщения и картинки в воздухе, опираясь на этот эффект . ===== Что нам понадобится? ===== {{ :projects:povdisplay:povdisplay_parts.jpg?nolink&700 |}} - [[amp>product/iskra-mini?utm_source=proj&utm_campaign=povdisplay&utm_medium=wiki | Iskra Mini (с ногами)]] - [[amp>product/troyka-accelerometer?utm_source=proj&utm_campaign=povdisplay&utm_medium=wiki | Акселерометр (Troyka-модуль)]] - [[amp>product/breadboard?utm_source=proj&utm_campaign=povdisplay&utm_medium=wiki | Макетная плата (Breadboard)]] - [[amp>product/led-5mm?utm_source=proj&utm_campaign=povdisplay&utm_medium=wiki | Светодиоды красные 5 мм]] (8 шт.) - [[amp>product/resistor?utm_source=proj&utm_campaign=keylogger&utm_medium=wiki | Резисторы 220 Ом]] (8 шт.) - [[amp>product/pin-headers?utm_source=proj&utm_campaign=povdisplay&utm_medium=wiki | Штырьковые соединители (1×40)]] - [[amp>product/wire-mm?utm_source=proj&utm_campaign=povdisplay&utm_medium=wiki | Соединительные провода «папа-папа»]] - [[amp>product/wire-fm?utm_source=proj&utm_campaign=povdisplay&utm_medium=wiki | Соединительные провода «мама-папа»]] - [[amp>product/21mm-screw-slot?utm_source=proj&utm_campaign=povdisplay&utm_medium=wiki | Гнездо питания 2,1 мм с клеммником]] - [[amp>product/krona-battery?utm_source=proj&utm_campaign=povdisplay&utm_medium=wiki | Батарейка Крона]] - [[amp>product/krona-21mm-cable?utm_source=proj&utm_campaign=povdisplay&utm_medium=wiki | Кабель питания от батарейки Крона]] ===== Как собрать? ===== - Так как акселерометр общается с управляющей электроникой по протоколу I²C, а на плате Iskra Mini эти пины не выведены, сделаем это сами. Для этого возьмите штырьковые соединители, откусите от них два пина и припаяйте с обратной стороны платы Iskra Mini к пинам ''A4 (SDA)'' и ''A5 (SCL)''.{{ :projects:povdisplay:povdisplay_build1.jpg?nolink&700 |}} - Установите платформу Iskra Mini на макетную плату. - Пины питания ''+5V'' и земли ''GND'' платформы Iskra Mini соедините с помощью проводов «папа-папа» с рельсами питания и земли макетной платы соответственно.{{ :projects:povdisplay:povdisplay_build2.jpg?nolink&700 |}} - Установите акселерометр на Breadbord и с помощью проводов подключите следующим образом: * Пины питания ''V'' и земли ''G'' акселерометра с помощью проводов «папа-папа» соедините с рельсами питания и земли макетной платы соответственно. * Сигнальные пины ''D'' и ''C'' акселерометра с помощью проводов «папа-мама» соедините с пинами ''A4 (SDA)'' и ''A5 (SCL)'' платы Iskra Mini.{{ :projects:povdisplay:povdisplay_build3.jpg?nolink&700 |}} - Возьмите 8 красных светодиодов и установите их в ряд на макетную плату.{{ :projects:povdisplay:povdisplay_build4.jpg?nolink&700 |}} - Далее с помощью резисторов ''220 Ом'' соедините катоды светодиодов с рельсой земли макетной платы.{{ :projects:povdisplay:povdisplay_build5.jpg?nolink&700 |}} - В продолжение используя провода «папа-папа» соедините аноды светодиодов со ''2'' по ''9'' пин платы Iska Mini.{{ :projects:povdisplay:povdisplay_build6.jpg?nolink&700 |}} - Переверните макетную плату. Возьмите кабель питания от батарейки Крона и соедините одну часть с Кроной, а вторую — с гнездом питания 2,1 мм с клеммником. Далее подключите в клеммник два провода «папа-папа». Теперь аккуратно отклейте защитную плёнку от breadbord и закрепите всю собранную конструкцию на липкой части макетки.{{ :projects:povdisplay:povdisplay_build7.jpg?nolink&700 |}} - В заключение подключите выведенные от клеммника два провода к платформе Iskra Mini таким образом: * контакт ''+'' клеммника — на ''Vin'' платы Iskra Mini; * контакт ''−'' клеммника — на ''GND'' платы Iskra Mini.{{ :projects:povdisplay:povdisplay_build8.jpg?nolink&700 |}} В итоге получилась схема, приведенная ниже. {{ :projects:povdisplay:povdisplay_scheme.png?nolink |}} ===== Алгоритм ===== * Сразу после подачи питания считываем направления и величину ускорения по оси Y. * Если ускорение превышает норму и направленно слева направо выводим буквы в обычном порядке. * Если ускорение превышает норму и направленно справа налево выводим буквы в обратно порядке зеркально отраженные. * Гасим все светодиоды и повторяем весь выше описанный цикл. ===== Исходный код ===== // библиотека для работы I²C #include // библиотека для работы с модулями IMU #include // задержка между столбцами одного символа // ширина символа #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)); } } ===== Демонстрация работы устройства ===== {{youtube>8QRj4CZWyX0?large}} ===== Что дальше? ===== В этот примере мы показали самое простое применение персистенции. Добавив механическую конструкцию, увеличив количество и разнообразив цвета светодиодов можно собрать более сложные и интереснее устройства. А если использовать RGB-светодиоды, можно собрать полноценный виртуальный дисплей в воздухе.