LED матрица на микроконтроллере ATmega328p или самопальный модуль Arduino. Часть первая — Разработка

    Добрый день, Geeks, хочу сегодня рассказать про эту достаточно простую и, тем не менее, интересную LED-матрицу, которую я собрал еще в 2017 году в сентябре. Пришло время простым и понятным языком рассказать всем, кто хоть как-то знаком с электроникой и программированием, как сделать такое устройство.

    image

    Это устройство может стать приятным подарком или украшением полки любого человека.

    Компоненты


    Приступим. Первое, что нужно для разработки любого устройства – это, как минимум, подготовить все необходимые радиокомпоненты или хотя бы основные составляющие.
    image
    1. Резисторы 150 ом 0.25 Ватт — 7 шт.
    2. Конденсаторы 50 вольт 1 микрофарад — 5 шт.
    3. Кварцевый резонатор 16 мгц — 1 шт.
    4. Разъём типа гребёнка ( потребуется 18 контактов ) — 1 шт.
    5. Сдвиговый регистр 74ch595 корпус DIP — 1 шт.
    6. Панель под микросхему 74ch595 корпус DIP (16 ножек) — 1 шт.
    7. Светодиоды 5мм 3 вольта — 35 шт.
    8. Микроконтроллер ATmega328p корпус DIP — 1 шт.
    9. Панель под микросхему ATmega328p корпус DIP (28 ножек) — 1 шт.
    10. Монтажная плата 5x7 — 1 шт.

    Рекомендую при необходимости купить флюс, припой и паяльник.

    Я намеренно не указываю марку проводов, которая вам подойдет, так как совсем не владею информацией об их параметрах. Могу посоветовать МГТФ, вполне возможно, что подойдут. Если вы знаете, какие провода точно оптимальны, оставьте информацию в комментариях или напишите мне в личные сообщения jsusdev.

    Я не буду вам советовать какие-либо конкретные интернет магазины, потому что это было давно и не правда.

    Сдвиговый регистр 74ch595


    Наверное, многим новичкам станет не по себе от понимания принципов работы микросхемы 74ch595 вне этой статьи. Сейчас я попробую максимально доступно объяснить, как она работает и чем будет полезна в конкретном случае с моей LED-матрицей. 

    Проще говоря, микросхема предназначена для увеличения количества цифровых пинов.

    Распиновка. Внимание! Рисунок имеет незначительные неточности в маркировке контактов, это сделано для более простого усвоения и понимания работы.

    image
    Самые загадочные контакты управления, которые вызывают интерес: 

    • output pin * — контакты вывода
    • DS — (Serial Data Input) контакт который определяет состояние напряжения на контактах вывода
    • SH — (Shift Register Clock Input) контакт который записывает состояние которое определенно в DS
    • ST — (Storage Register Clock Input) контакт который открывает микросхему для записи и закрывает, устанавливая на контакты вывода нужные состояния определенные DS

    Уверен, визуальный пример поможет вам осознать происходящее лучше.

    image

    Если нет, то я оставил и интерактивную версию:74ch595.swf, все кнопочки работают, можно понажимать.

    Если вы осознали, как это работает, то единственное, что мне осталось, так это добавить пример кода для работы с микросхемой.

    Этот код только для окончательного понимания как это работает программно. Код, который можно загрузить, будет в спойлере ниже.

    int ST = 7;
    int SH = 6;
    int DS = 5;
    
    digitalWrite(ST, 1); // начало записи в байт
    
      for (int i = 0; i < 8; i++) {
    
        digitalWrite(SH, 1); // начало записи бита 
    
          digitalWrite(DS, 1); // < --- последний аргумент это бит который будет записан
    
        digitalWrite(SH, 0); // конец записи бита
    
      }
    
    digitalWrite(ST, 0); // конец записи в байт
    
    

    Код который будет работать
    int ST = 7;
    int SH = 6;
    int DS = 5;
    
    void setup() {
      
      pinMode(7, 1);
      pinMode(6, 1);
      pinMode(5, 1);
    
    }
    
    void loop() {
    
      digitalWrite(ST, 1); // начало записи в байт
      
        for (int i = 0; i < 8; i++) {
    
          digitalWrite(SH, 1); // начало записи бита
    
            digitalWrite(DS, 1); // < --- последний аргумент это бит который будет записан
    
          digitalWrite(SH, 0); // конец записи бита
    
        }
      
      digitalWrite(ST, 0); // конец записи в байт
    
    }
    


    Теперь, когда вы овладели работой с микросхемой, можно приступить к следующему пункту.

    Подготовка к разработке матрицы


    В моем случае светодиоды мешали друг другу на плате, и чтобы светодиоды «встали» на плату необходимо обточить мелкую окантовку, если конечно это требуется. Внимание! Нужно именно обточить, потому что есть еще немало способов от нее избавиться. Так как светодиоды на самом деле очень хрупкие, желательно обрабатывать их аккуратно, процесс выпаивания в случае чего мне ни разу не давался легко.
    image
    Хотелось бы обратить особое внимание на конденсаторы, я не могу объяснить их необходимость специальным языком, но без них светодиоды не затухают до конца и от этого устройство выглядит неоптимизированным, конденсаторы стабилизируют ток между контроллером и светодиодом.

    image

    В финальном исполнении установить конденсаторы получилось только так, хотя, раскрою секрет, подобное устройство было не одно, на одном из них конденсаторы крепились с обратной стороны на одну ножку в том месте, где сейчас проходит провод питания к 74ch595.

    Не стоит беспокоиться о количестве проводов на фото, далее покажу как все устроено.

    image

    Совсем забыл, и вы, наверное, упустили из внимания, что на устройстве визуально расположено всего 6 резисторов, но на самом деле их 7: один из них находится прямо под 74ch595.

    image

    Стоит добавить, что кварцевый генератор (16 МГц) можно заменить на 24 МГц, это существенно ускорит работу, но такие изменения применимы только к готовому устройству, так как ATmega328p просто не прошивается на кварцевом генераторе 24 МГц.

    Разработка матрицы


    Принципиальная схема устройства.
    image

    Внимание! Светодиоды расположены «минусом» к ATmega328p и в тоже время «плюсом» к 74ch595. Расположение компонентов на плате.
    image
    Обратная часть (вариант без конденсаторов)
    image
    3D-модель, которую можно повертеть в редакторе Google SketchUp.
    Саму модель можно скачать тут: 3D_Schema.skp

    Когда мы запаяли все компоненты, можно припаивать провода.
    image
    Стартуем:
    image
    Соединяем контакты rx, tx, reset и светодиоды. (светодиоды соединены отрезанными от них ножками):
    image
    Соединяем компоненты питание и кварцевый генератор:
    image
    Соединяем светодиоды с 74ch595:
    image
    Соединяем светодиоды с ATmega328p и ATmega328p и 74ch595:
    image
    Соединяем конденсаторы:
    image
    Поздравляю физическая часть закончена:
    image
    Наверняка устройство сразу не оживет без прошивки, но можно попробовать стандартный blink Arduino. (Да, в некоторых случаях действительно может заработать).

    У меня Blink заработал. Если у вас хоть что-то на первом ряду будет мигать значит все ок, если нет – читаем дальше.

    image

    Разработка программы


    Чтобы микроконтроллер управлял своими контактами, их надо «инициализировать».
    Да, я мог сделать иначе, например, использовать массив и цикл, чтобы сократить количество кода в функции. Вообще, множество программных решений, которые я принял в процессе реализации, губительны с какой-то позиции, например, для программируемой памяти (Flash), но позитивны для динамической (RAM). Мне удалось снизить использование динамической памяти с 69% до 23%.
    Хочу отдельно заметить, что отсутствие динамической памяти очень критично для устройства: оно просто может зависнуть в любой момент из-за нехватки памяти, в данном случае, динамической памяти всего 2 Кб, а программируемой 32 Кб.

    Поэтому примите и смиритесь или сделайте лучше!

    
    // функция инициализации 
    
    void init() {
    
        pinMode(5, 1);
        pinMode(6, 1);
        pinMode(7, 1);
        pinMode(9, 1);
        pinMode(10, 1);
        pinMode(11, 1);
        pinMode(12, 1);
        pinMode(13, 1);
        
        digitalWrite(5,0);
        digitalWrite(6,0);
        digitalWrite(7,0);
        digitalWrite(9,0);
        digitalWrite(10,0);
        digitalWrite(11,0);
        digitalWrite(12,0);
        digitalWrite(13,0);
        
      };
    
    

    Для управления матрицей я разработал такую функцию. Она является ключевой по сути.

    
    void path(char bits[5]) {
        
        for (int l = 0;l < 5;l++) {
          
          digitalWrite(9, 1);
          digitalWrite(10, 1);  
          digitalWrite(11, 1);  // эта часть просто отключает пины
          digitalWrite(12, 1);    
          digitalWrite(13, 1);  
    
          if (l == 1) digitalWrite(13, 0);
          if (l == 2) digitalWrite(12, 0);
          if (l == 3) digitalWrite(11, 0); // эта часть включает пины в зависимости от условия
          if (l == 4) digitalWrite(10, 0);
          if (l == 0) digitalWrite(9, 0);
    
          
          // эта часть нужна для управления регистром, см. пункт выше
        
          digitalWrite(7, 1); 
            for (int i = 0; i < 8;i++) {
              digitalWrite(6, 1);
               digitalWrite(5, (bits[l] >> i) & 1);  
    
               /* (bits[l] >> i) 
                * такой вот способ преобразовывать 
                * из 10 в 2, спасибо 
                * чуваку который мне 
                * помог с этим условием  
                */
    
              digitalWrite(6, 0);
            }
          digitalWrite(7, 0);
          
          delay(2); // каким то образом эта строчка помогает избавиться мерцания
        }
      };
      
    

    Каждый элемент массива – это одна колонка светодиодов.

    
    char Matrx_symbol[5] = { 127, // 1
                             0,   // 2
                             0,   // 3
                             0,   // 4
                             0    // 5
                           };
    
    path(Matrx_symbol);
    
    

    Нумерация колонок идет справа налево, то есть зеркально массиву.
    image
    Что это за магические цифры в массиве?
    image
    Проще говоря, колонка светодиодов – это байт — 1 бит, вмещающий число в десятичной системе счисления от 0 до 127, этим числом мы определяем, каким светодиодам нужно зажечься. 

    Конечно, мы не будем собирать свои символы путем грубого или осознанного перебора бит в байте. Я разработал страницу, на которой можно сформировать любой символ Symbol creater.

    image

    Все предельно просто и легко, мы просто выбираем курсором нужны светодиоды и получаем массив, который можно использовать в качестве символа.

    image

    Функция, в которой определены символы. Изначально я планировал, что функцию можно будет совершенствовать, расширяя количество символов. Сейчас их 87, но я предполагал, что их может быть до 255. Но увы, наверное, в этом релизе нет, так как интерпретатор Arduino не определяет символы, код которых выше чем 127.

    Буквально только что я об этом узнал в 5:31 по МСК

    image

    Функция, которая конвертирует строку в набор перебираемых символов, принимает два параметра:
    1. саму строку «Hellow world!»
    2. время между переключением символов в мс.


    Функция которая конвертирует строку в набор перебираемых символов. Принимает два параметра это саму строку «Hellow world!» и вторым параметром время между переключением символов в мс.

    Много кода
    
    void message(String text, int Delay) {
    
        int len = text.length();
    
        for (int i = 0; i < len; i++) {      
         
          for (int Delay_ = 0; Delay_ < Delay/15; Delay_++){
    
           if (text[i] == 32) { char Symbol[5] = {0,0,0,0,0}; path(Symbol); } // 
           if (text[i] == 43) { char Symbol[5] = {8,8,62,8,8}; path(Symbol); } // +
           if (text[i] == 45) { char Symbol[5] = {8,8,8,8,8}; path(Symbol); } // -
           if (text[i] == 61) { char Symbol[5] = {20,20,20,20,20}; path(Symbol); } // =
           if (text[i] == 95) { char Symbol[5] = {1,1,1,1,1}; path(Symbol); } // _
           if (text[i] == 41) { char Symbol[5] = {62,65,0,0,0}; path(Symbol); } // )
           if (text[i] == 40) { char Symbol[5] = {0,0,0,65,62}; path(Symbol); } // (
           if (text[i] == 123) { char Symbol[5] = {0,65,65,54,8}; path(Symbol); } // {
           if (text[i] == 125) { char Symbol[5] = {8,54,65,65,0}; path(Symbol); } // }
           if (text[i] == 62) { char Symbol[5] = {8,20,34,65,0}; path(Symbol); } // >
           if (text[i] == 60) { char Symbol[5] = {0,65,34,20,8}; path(Symbol); } // <
           if (text[i] == 46) { char Symbol[5] = {0,0,1,0,0}; path(Symbol); } // .
           if (text[i] == 44) { char Symbol[5] = {0,0,3,2,0}; path(Symbol); } // ,
           if (text[i] == 63) { char Symbol[5] = {48,72,69,64,48}; path(Symbol); } // ?
           if (text[i] == 33) { char Symbol[5] = {0,0,125,0,0}; path(Symbol); } // !
           if (text[i] == 64) { char Symbol[5] = {58,69,93,65,62}; path(Symbol); } // @
           if (text[i] == 35) { char Symbol[5] = {20,127,20,127,20}; path(Symbol); } // #
           if (text[i] == 36) { char Symbol[5] = {38,73,127,73,50}; path(Symbol); } // $
           if (text[i] == 37) { char Symbol[5] = {19,11,4,50,49}; path(Symbol); } // %
           if (text[i] == 94) { char Symbol[5] = {0,32,64,32,0}; path(Symbol); } // ^
           if (text[i] == 58) { char Symbol[5] = {0,0,0,34,0}; path(Symbol); } // :
           if (text[i] == 42) { char Symbol[5] = {8,20,42,20,8}; path(Symbol); } // * 
           if (text[i] == 38) { char Symbol[5] = {2,53,73,73,54}; path(Symbol); } // &
           if (text[i] == 34) { char Symbol[5] = {0,112,0,112,0}; path(Symbol); } // "
           if (text[i] == 59) { char Symbol[5] = {0,0,0,38,2}; path(Symbol); } // ;
           if (text[i] == 48) { char Symbol[5] = {62,65,65,65,62}; path(Symbol); } // 0
           if (text[i] == 49) { char Symbol[5] = {0,1,127,33,16}; path(Symbol); } // 1
           if (text[i] == 50) { char Symbol[5] = {49,73,69,67,49}; path(Symbol); } // 2
           if (text[i] == 51) { char Symbol[5] = {54,73,73,65,34}; path(Symbol); } // 3
           if (text[i] == 52) { char Symbol[5] = {5,127,37,20,12}; path(Symbol); } // 4
           if (text[i] == 53) { char Symbol[5] = {6,73,73,73,114}; path(Symbol); } // 5
           if (text[i] == 54) { char Symbol[5] = {38,73,73,73,62}; path(Symbol); } // 6
           if (text[i] == 55) { char Symbol[5] = {96,80,79,64,64}; path(Symbol); } // 7
           if (text[i] == 56) { char Symbol[5] = {54,73,73,73,54}; path(Symbol); } // 8
           if (text[i] == 57) { char Symbol[5] = {62,73,73,73,50}; path(Symbol); } // 9
           if (text[i] == 97) { char Symbol[5] = {1,62,37,37,18}; path(Symbol); } // a
           if (text[i] == 65) { char Symbol[5] = {3,28,36,28,3}; path(Symbol); } // A
           if (text[i] == 98) { char Symbol[5] = {6,9,9,9,126}; path(Symbol); } // b
           if (text[i] == 66) { char Symbol[5] = {6,57,73,73,127}; path(Symbol); } // B
           if (text[i] == 99) { char Symbol[5] = {18,33,33,33,30}; path(Symbol); } // c
           if (text[i] == 67) { char Symbol[5] = {34,65,65,65,62}; path(Symbol); } // C
           if (text[i] == 100) { char Symbol[5] = {126,9,9,9,6}; path(Symbol); } // d
           if (text[i] == 68) { char Symbol[5] = {62,65,65,65,127}; path(Symbol); } // D
           if (text[i] == 101) { char Symbol[5] = {26,37,37,37,30}; path(Symbol); } // e
           if (text[i] == 69) { char Symbol[5] = {65,65,73,73,127}; path(Symbol); } // E
           if (text[i] == 102) { char Symbol[5] = {0,32,36,31,4}; path(Symbol); } // f
           if (text[i] == 70) { char Symbol[5] = {64,64,72,72,127}; path(Symbol); } // F
           if (text[i] == 103) { char Symbol[5] = {62,73,73,74,48}; path(Symbol); } // g
           if (text[i] == 71) { char Symbol[5] = {38,73,73,65,62}; path(Symbol); } // G 
           if (text[i] == 104) { char Symbol[5] = {0,7,8,8,63}; path(Symbol); } // h 
           if (text[i] == 72) { char Symbol[5] = {127,8,8,8,127}; path(Symbol); } // H
           if (text[i] == 105) { char Symbol[5] = {0,0,47,0,0}; path(Symbol); } // i
           if (text[i] == 73) { char Symbol[5] = {0,65,127,65,0}; path(Symbol); } // I
           if (text[i] == 106) { char Symbol[5] = {0,94,1,1,2}; path(Symbol); } // j
           if (text[i] == 74) { char Symbol[5] = {126,1,1,1,6}; path(Symbol); } // J
           if (text[i] == 107) { char Symbol[5] = {0,16,9,6,63}; path(Symbol); } // k
           if (text[i] == 75) { char Symbol[5] = {64,33,18,12,127}; path(Symbol); } // K
           if (text[i] == 108) { char Symbol[5] = {0,1,63,0,0}; path(Symbol); } // l
           //if (text[i] == 76) { char Symbol[5] = {1,1,1,1,127}; path(Symbol); } // L
           if (text[i] == 109) { char Symbol[5] = {15,16,24,16,15}; path(Symbol); } // m
           if (text[i] == 77) { char Symbol[5] = {63,64,56,64,63}; path(Symbol); } // M
           if (text[i] == 110) { char Symbol[5] = {31,32,32,32,63}; path(Symbol); } // n
           if (text[i] == 78) { char Symbol[5] = {127,4,8,16,127}; path(Symbol); } // N
           if (text[i] == 111) { char Symbol[5] = {14,17,17,17,14}; path(Symbol); } // o
           if (text[i] == 79) { char Symbol[5] = {62,65,65,65,62}; path(Symbol); } // O
           if (text[i] == 112) { char Symbol[5] = {0,24,36,36,63}; path(Symbol); } // p
           if (text[i] == 80) { char Symbol[5] = {56,68,68,68,63}; path(Symbol); } // P
           if (text[i] == 113) { char Symbol[5] = {31,36,36,36,24}; path(Symbol); } // q
           if (text[i] == 81) { char Symbol[5] = {61,66,65,65,62}; path(Symbol); } // Q
           if (text[i] == 114) { char Symbol[5] = {0,48,16,32,63}; path(Symbol); } // r
           if (text[i] == 82) { char Symbol[5] = {56,69,70,68,63}; path(Symbol); } // R
           if (text[i] == 115) { char Symbol[5] = {0,18,37,41,18}; path(Symbol); } // s
           //if (text[i] == 83) { char Symbol[5] = {38,73,73,73,50}; path(Symbol); } // S
           if (text[i] == 116) { char Symbol[5] = {0,2,17,62,16}; path(Symbol); } // t
           if (text[i] == 84) { char Symbol[5] = {64,64,127,64,64}; path(Symbol); }// T
           if (text[i] == 117) { char Symbol[5] = {29,2,1,1,30}; path(Symbol); } // u
           if (text[i] == 85) { char Symbol[5] = {126,1,1,1,126}; path(Symbol); } // U
           if (text[i] == 118) { char Symbol[5] = {60,2,1,2,60}; path(Symbol); } // v
           if (text[i] == 86) { char Symbol[5] = {124,2,1,2,124}; path(Symbol); } // V
           if (text[i] == 119) { char Symbol[5] = {30,1,6,1,30}; path(Symbol); } // w
           if (text[i] == 87) { char Symbol[5] = {126,1,6,1,126}; path(Symbol); } // W
           if (text[i] == 120) { char Symbol[5] = {17,10,4,10,17}; path(Symbol); } // x
           if (text[i] == 88) { char Symbol[5] = {99,20,8,20,99}; path(Symbol); } // X
           if (text[i] == 121) { char Symbol[5] = {56,4,6,57,0}; path(Symbol); } // y
           if (text[i] == 89) { char Symbol[5] = {112,8,7,8,112}; path(Symbol); } // Y
           if (text[i] == 122) { char Symbol[5] = {17,25,21,19,17}; path(Symbol); } // z
           if (text[i] == 90) { char Symbol[5] = {97,81,73,69,67}; path(Symbol); } // Z
           if (text[i] == 76) { char Symbol[5] = {24,60,30,60,24}; path(Symbol); } // L
           if (text[i] == 83) { char Symbol[5] = {6,113,1,113,6}; path(Symbol); } // S   
          
          }
        }
    


    Теперь, когда мы «разобрали» принцип работы функций данной программы в контроллере, нужно его оформить.

    Очень много кода
    
    class Jsus {
      
      public:
      
      void init() {
        
        pinMode(5, 1);
        pinMode(6, 1);
        pinMode(7, 1);
        pinMode(9, 1);
        pinMode(10, 1);
        pinMode(11, 1);
        pinMode(12, 1);
        pinMode(13, 1);
        
        digitalWrite(5,0);
        digitalWrite(6,0);
        digitalWrite(7,0);
        digitalWrite(9,0);
        digitalWrite(10,0);
        digitalWrite(11,0);
        digitalWrite(12,0);
        digitalWrite(13,0);
        
      };
      
      void path(char bits[5]) {
        
        for (int l = 0;l < 5;l++) {
          
          digitalWrite(9, 1);
          digitalWrite(10, 1);  
          digitalWrite(11, 1);  
          digitalWrite(12, 1);    
          digitalWrite(13, 1);  
    
          if (l == 1) digitalWrite(13, 0);
          if (l == 2) digitalWrite(12, 0);
          if (l == 3) digitalWrite(11, 0);
          if (l == 4) digitalWrite(10, 0);
          if (l == 0) digitalWrite(9, 0);
    
          
          digitalWrite(7, 1);
            for (int i = 0; i < 8;i++) {
              digitalWrite(6, 1);
               digitalWrite(5, (bits[l] >> i) & 1);  
              digitalWrite(6, 0);
            }
          digitalWrite(7, 0);
          
          delay(2);
        }
      };
      
      void message(String text, int Delay) {
    
        int len = text.length();
    
        for (int i = 0; i < len; i++) {      
         
          for (int Delay_ = 0; Delay_ < Delay/15; Delay_++){
    
           if (text[i] == 32) { char Symbol[5] = {0,0,0,0,0}; path(Symbol); } // 
           if (text[i] == 43) { char Symbol[5] = {8,8,62,8,8}; path(Symbol); } // +
           if (text[i] == 45) { char Symbol[5] = {8,8,8,8,8}; path(Symbol); } // -
           if (text[i] == 61) { char Symbol[5] = {20,20,20,20,20}; path(Symbol); } // =
           if (text[i] == 95) { char Symbol[5] = {1,1,1,1,1}; path(Symbol); } // _
           if (text[i] == 41) { char Symbol[5] = {62,65,0,0,0}; path(Symbol); } // )
           if (text[i] == 40) { char Symbol[5] = {0,0,0,65,62}; path(Symbol); } // (
           if (text[i] == 123) { char Symbol[5] = {0,65,65,54,8}; path(Symbol); } // {
           if (text[i] == 125) { char Symbol[5] = {8,54,65,65,0}; path(Symbol); } // }
           if (text[i] == 62) { char Symbol[5] = {8,20,34,65,0}; path(Symbol); } // >
           if (text[i] == 60) { char Symbol[5] = {0,65,34,20,8}; path(Symbol); } // <
           if (text[i] == 46) { char Symbol[5] = {0,0,1,0,0}; path(Symbol); } // .
           if (text[i] == 44) { char Symbol[5] = {0,0,3,2,0}; path(Symbol); } // ,
           if (text[i] == 63) { char Symbol[5] = {48,72,69,64,48}; path(Symbol); } // ?
           if (text[i] == 33) { char Symbol[5] = {0,0,125,0,0}; path(Symbol); } // !
           if (text[i] == 64) { char Symbol[5] = {58,69,93,65,62}; path(Symbol); } // @
           if (text[i] == 35) { char Symbol[5] = {20,127,20,127,20}; path(Symbol); } // #
           if (text[i] == 36) { char Symbol[5] = {38,73,127,73,50}; path(Symbol); } // $
           if (text[i] == 37) { char Symbol[5] = {19,11,4,50,49}; path(Symbol); } // %
           if (text[i] == 94) { char Symbol[5] = {0,32,64,32,0}; path(Symbol); } // ^
           if (text[i] == 58) { char Symbol[5] = {0,0,0,34,0}; path(Symbol); } // :
           if (text[i] == 42) { char Symbol[5] = {8,20,42,20,8}; path(Symbol); } // * 
           if (text[i] == 38) { char Symbol[5] = {2,53,73,73,54}; path(Symbol); } // &
           if (text[i] == 34) { char Symbol[5] = {0,112,0,112,0}; path(Symbol); } // "
           if (text[i] == 59) { char Symbol[5] = {0,0,0,38,2}; path(Symbol); } // ;
           if (text[i] == 48) { char Symbol[5] = {62,65,65,65,62}; path(Symbol); } // 0
           if (text[i] == 49) { char Symbol[5] = {0,1,127,33,16}; path(Symbol); } // 1
           if (text[i] == 50) { char Symbol[5] = {49,73,69,67,49}; path(Symbol); } // 2
           if (text[i] == 51) { char Symbol[5] = {54,73,73,65,34}; path(Symbol); } // 3
           if (text[i] == 52) { char Symbol[5] = {5,127,37,20,12}; path(Symbol); } // 4
           if (text[i] == 53) { char Symbol[5] = {6,73,73,73,114}; path(Symbol); } // 5
           if (text[i] == 54) { char Symbol[5] = {38,73,73,73,62}; path(Symbol); } // 6
           if (text[i] == 55) { char Symbol[5] = {96,80,79,64,64}; path(Symbol); } // 7
           if (text[i] == 56) { char Symbol[5] = {54,73,73,73,54}; path(Symbol); } // 8
           if (text[i] == 57) { char Symbol[5] = {62,73,73,73,50}; path(Symbol); } // 9
           if (text[i] == 97) { char Symbol[5] = {1,62,37,37,18}; path(Symbol); } // a
           if (text[i] == 65) { char Symbol[5] = {3,28,36,28,3}; path(Symbol); } // A
           if (text[i] == 98) { char Symbol[5] = {6,9,9,9,126}; path(Symbol); } // b
           if (text[i] == 66) { char Symbol[5] = {6,57,73,73,127}; path(Symbol); } // B
           if (text[i] == 99) { char Symbol[5] = {18,33,33,33,30}; path(Symbol); } // c
           if (text[i] == 67) { char Symbol[5] = {34,65,65,65,62}; path(Symbol); } // C
           if (text[i] == 100) { char Symbol[5] = {126,9,9,9,6}; path(Symbol); } // d
           if (text[i] == 68) { char Symbol[5] = {62,65,65,65,127}; path(Symbol); } // D
           if (text[i] == 101) { char Symbol[5] = {26,37,37,37,30}; path(Symbol); } // e
           if (text[i] == 69) { char Symbol[5] = {65,65,73,73,127}; path(Symbol); } // E
           if (text[i] == 102) { char Symbol[5] = {0,32,36,31,4}; path(Symbol); } // f
           if (text[i] == 70) { char Symbol[5] = {64,64,72,72,127}; path(Symbol); } // F
           if (text[i] == 103) { char Symbol[5] = {62,73,73,74,48}; path(Symbol); } // g
           if (text[i] == 71) { char Symbol[5] = {38,73,73,65,62}; path(Symbol); } // G 
           if (text[i] == 104) { char Symbol[5] = {0,7,8,8,63}; path(Symbol); } // h 
           if (text[i] == 72) { char Symbol[5] = {127,8,8,8,127}; path(Symbol); } // H
           if (text[i] == 105) { char Symbol[5] = {0,0,47,0,0}; path(Symbol); } // i
           if (text[i] == 73) { char Symbol[5] = {0,65,127,65,0}; path(Symbol); } // I
           if (text[i] == 106) { char Symbol[5] = {0,94,1,1,2}; path(Symbol); } // j
           if (text[i] == 74) { char Symbol[5] = {126,1,1,1,6}; path(Symbol); } // J
           if (text[i] == 107) { char Symbol[5] = {0,16,9,6,63}; path(Symbol); } // k
           if (text[i] == 75) { char Symbol[5] = {64,33,18,12,127}; path(Symbol); } // K
           if (text[i] == 108) { char Symbol[5] = {0,1,63,0,0}; path(Symbol); } // l
           //if (text[i] == 76) { char Symbol[5] = {1,1,1,1,127}; path(Symbol); } // L
           if (text[i] == 109) { char Symbol[5] = {15,16,24,16,15}; path(Symbol); } // m
           if (text[i] == 77) { char Symbol[5] = {63,64,56,64,63}; path(Symbol); } // M
           if (text[i] == 110) { char Symbol[5] = {31,32,32,32,63}; path(Symbol); } // n
           if (text[i] == 78) { char Symbol[5] = {127,4,8,16,127}; path(Symbol); } // N
           if (text[i] == 111) { char Symbol[5] = {14,17,17,17,14}; path(Symbol); } // o
           if (text[i] == 79) { char Symbol[5] = {62,65,65,65,62}; path(Symbol); } // O
           if (text[i] == 112) { char Symbol[5] = {0,24,36,36,63}; path(Symbol); } // p
           if (text[i] == 80) { char Symbol[5] = {56,68,68,68,63}; path(Symbol); } // P
           if (text[i] == 113) { char Symbol[5] = {31,36,36,36,24}; path(Symbol); } // q
           if (text[i] == 81) { char Symbol[5] = {61,66,65,65,62}; path(Symbol); } // Q
           if (text[i] == 114) { char Symbol[5] = {0,48,16,32,63}; path(Symbol); } // r
           if (text[i] == 82) { char Symbol[5] = {56,69,70,68,63}; path(Symbol); } // R
           if (text[i] == 115) { char Symbol[5] = {0,18,37,41,18}; path(Symbol); } // s
           //if (text[i] == 83) { char Symbol[5] = {38,73,73,73,50}; path(Symbol); } // S
           if (text[i] == 116) { char Symbol[5] = {0,2,17,62,16}; path(Symbol); } // t
           if (text[i] == 84) { char Symbol[5] = {64,64,127,64,64}; path(Symbol); }// T
           if (text[i] == 117) { char Symbol[5] = {29,2,1,1,30}; path(Symbol); } // u
           if (text[i] == 85) { char Symbol[5] = {126,1,1,1,126}; path(Symbol); } // U
           if (text[i] == 118) { char Symbol[5] = {60,2,1,2,60}; path(Symbol); } // v
           if (text[i] == 86) { char Symbol[5] = {124,2,1,2,124}; path(Symbol); } // V
           if (text[i] == 119) { char Symbol[5] = {30,1,6,1,30}; path(Symbol); } // w
           if (text[i] == 87) { char Symbol[5] = {126,1,6,1,126}; path(Symbol); } // W
           if (text[i] == 120) { char Symbol[5] = {17,10,4,10,17}; path(Symbol); } // x
           if (text[i] == 88) { char Symbol[5] = {99,20,8,20,99}; path(Symbol); } // X
           if (text[i] == 121) { char Symbol[5] = {56,4,6,57,0}; path(Symbol); } // y
           if (text[i] == 89) { char Symbol[5] = {112,8,7,8,112}; path(Symbol); } // Y
           if (text[i] == 122) { char Symbol[5] = {17,25,21,19,17}; path(Symbol); } // z
           if (text[i] == 90) { char Symbol[5] = {97,81,73,69,67}; path(Symbol); } // Z
           if (text[i] == 76) { char Symbol[5] = {24,60,30,60,24}; path(Symbol); } // L
           if (text[i] == 83) { char Symbol[5] = {6,113,1,113,6}; path(Symbol); } // S   
          
          }
        }
      };  
      
    };
    
    Jsus matrix;
    
    


    Код для старта.

    
    void setup() {
    
      matrix.init();
    
    }
    
    
    void loop() {
    
       matrix.message("Fuck RKN!", 400);
    
    }
    
    

    Тут можно найти готовый к загрузке в Arduino cкетч.

    Результаты разработки


    2018. Текущая версия устройства:



    2017. Текущая версия устройства (не оптимизированная):



    2016. Первая тестовая версия устройства. Еще даже без микросхем на плате:



    Все необходимые ссылки можно найти в репозитории на Github проекта: Arduino-matrix-module.

    Если статья не ответила на какие-то вопросы реализации, пишите их в комментариях.

    Всё, вроде ничего не забыл. Поки чмоки лавки лавки.
    Поделиться публикацией
    Ой, у вас баннер убежал!

    Ну. И что?
    Реклама
    Комментарии 41
    • 0
      Когда я первый раз после многолетнего перерыва взял в руки паяльник и спаял себе на макетке программатор для AVR и он даже заработал у меня был аналогичный восторг) До того приходилось иметь дело только со всякой микросхемной рассыпухой в радиокружке, а тут вдруг оказалось что технологии ого-го как шагнули далеко вперед за время моего перерыва в радиолюбительстве.
      А если по делу, то контроллер тут не нужен, двух регистров достаточно.
      • 0
        влепите ему плюсов! это самая наглядная статья для чайников из всех что я когда либо видел!
        • 0
          В качестве бонуса ещё бы схему без МК, а с кнопками как на гифке, чтоб самые маленькие могли пощупать шайтан-машину — сдвиговый регистр.
        • 0
          Обойтись то можно и регистрами если палатой управлять извне, но думаю что вы понимаете что те провода которые идут извне это — питание и прошивка все управление происходит на контроллером на плате.
          • 0
            Сколько же нервов пьёт подобное расположение выходов (я про питание и землю) при разводке односторонней платы.
            image
            На логической схеме всё хорошо и находится с одной стороны, а на деле приходится танцевать с бубном…
            • 0
              Одна перемычка под микросхемой, которую и вовсе не видно если смотреть с эстетической стороны.
            • +1

              Опыт — это хорошо, но сомнительность данного применения в виде "как есть".

              • +1
                Светодиоды есть и в цилиндрических корпусах (в т.ч. и с плоским торцом), и в «квадратном» корпусе. в чем сакральный смысл «обточки»?
                • 0
                  Я отталкиваюсь о того что было у меня, у меня был только этот тип светодиодов, поэтому чтоб все светодиоды были равны и не было никаких смещений пришлось обточить.
                  • 0
                    Принял к сведению, исправил.
                  • 0
                    Такого варианта «помигать светодиодом» я ещё не видел, Зачёт!!! ))
                    • +3
                      Электролитические конденсаторы прямо на портах МК? Зачем так издеваться над МК? Это же большой импульсный ток, рано или поздно пины повыгорают.
                      • 0
                        Спасибо за наглядный туториал.
                        Вы в SketchUp модель выгружали или прям в нём делали? Люблю его за простоту, корпуса в нём прикидывать хорошо, но вот рисовать плату с нуля (без готовых компонентов) я ленился.

                        И вопрос к знатокам Arduino: код вида
                              digitalWrite(9, 1);
                              digitalWrite(10, 1);  
                              digitalWrite(11, 1);
                              digitalWrite(12, 1);    
                              digitalWrite(13, 1);  
                        преобразуется в маски по числу задействованных регистров, или по-пионерски — сколько строк, столько отдельных команд?
                        • 0
                          Почти все компоненты рисовал прямо в SketchUp, кроме платы ее просто наложил на блок как текстуру.
                          • 0
                            «А вы то сами как думаете?»
                            Если хотите по-взрослому, то Александреску Вам в помощь, а в самой Ардуино все по-пионерски.
                            • 0
                              Сурово. Я думал, что за столько-то лет разработки базовые функции оптимизировали.
                              Александреску я не осилил толком, но такие вещи и на макросах можно разматывать.
                              • 0

                                Хорошая статья на тему: Работа с портами ввода-вывода микроконтроллеров на Си++.
                                С Variadic Templates всё становится без черной магии с меньшим количеством черной магии.


                                Может быть, еще можно через C++14 constexpr сделать, циклами. Тогда будет совсем без магии. Хотя, там вроде функции без побочных эффектов должны быть? Я еще в этой теме не совсем разобрался.

                                • 0
                                  Читал, спасибо. Я такой код прочесть и понять смогу, у меня с написанием кода на шаблонах большие нелады.
                                  • 0

                                    Кстати, кто-нибудь знает способ, как убедится, что constexpr-функция действительно constexpr? Я пытался смотреть дизассемблерный код, но там компилятор (ARM Compiler 6) и так инлайнит. А без оптимизации он вроде и constexpr отказывается делать… Может, ошибаюсь.

                            • 0
                              вам порты меги не жалко?
                              1) при зажигании 7 светодиодов нагрузка на порт будет = 7*(5-2)/150 = 140мА (по даташиту если память не изменяет память на пин порта около 20мА максимум);
                              2) акромя вкорячивания 140мА в светодиоды бонусом еще надо заряжать электролиты, т.е. еще дополнительная токовая нагрузка.
                              • 0
                                Так я понял смысл электролитов как раз в том, чтобы обеспечить пиковый ток при включенных светодиодах, а потом во время паузы их подзарядить. Но последовательные резисторы по любому нужны.
                                • +1
                                  хм. динамическая индикация вроде как, тут электролиты как раз мешают. И на верхнее плечо лучше поставить таки нормальные транзисторные ключи (это убережет мегу от токового изнасилования).
                                  виной тому кстати криво написаный код + адурина, как сделано:
                                  1) гасим индикатор (выкл. все линии)
                                  2) вдвигаем данные в регистр
                                  3) щелкаем регистром (введенные данные появляются на выходе)
                                  4) включаем линию.
                                  п.2 — самые затратный по времени, по идее надо так:
                                  1) вдвигаем данные в регистр
                                  2) гасим индикатор (выкл. все линии)
                                  3) щелкаем регистром (введенные данные появляются на выходе)
                                  4) включаем линию.
                                  • 0
                                    правильное динамо

                                    и никаких конденсаторов не надо.
                                • 0
                                  Ну, если автор маленько перегрел конденсаторы при пайке или взял БУ-ные, то всё не так страшно. Да и с другой стороны диодов у него сдвиговый регистр — там тоже немного просядет (хотя микроконтроллер сдохнет первым, конечно).
                                  • 0
                                    Да конденсаторы БУ причем из какой то совсем старой техники.
                                  • 0
                                    Я наверно не столь компетентен как вы, но основываясь на моем опыте который показывает что не один из контроллеров не потерял свои свойства пока я его использовал во всех позах я могу сделать вывод что если выводы и погорят то это будет еще очень не скоро.

                                    Да я могу согласиться что было бы просто великолепно если бы контроллер управлял транизисторами, а они в свою очередь светодиодами — это было бы гуманней по отношению к контроллеру, поэтому принял к сведению постараюсь больше не косячить.
                                    • 0
                                      Дело не в компетенции, просто есть вещи, которые следует делать не задумываясь — ставить ограничивающие ток резисторы или умощняющие транзисторы. И только хорошенько подумав и убедившись, что все правильно получается, можно их не ставить. Ну а аргумент «ведь работает же» — это не для инженерного дела (надеюсь, мы именно им занимаемся).
                                      • 0
                                        в журнале Микропроцессорные средства и системы году в 1985 был шутливый тест «являетесь ли вы инженером» (у них вообще на последнем развороте был раздел юмора). ну и там вопрос типа:
                                        «каково напряжение питания микросхемы ***» с вариантами ответов
                                        1)«5 вольт»
                                        2)«9 вольт»
                                        3)«я подал 12, и до сих пор работает»…

                                  • 0
                                    Вообще то, после таких программ (смотри листинги в посте) я начинаю понимать Ардуино-хэйтеров, хотя сам к ним не отношусь.
                                    • 0
                                      Платку бы развести… для версии 3.0
                                      • 0
                                        А чего не готовый модуль с Али за 1.7$?
                                        • 0
                                          Я не планирую производить такие модули в больших объемах, вообще это устройство насколько я помню родилось только потому что я хотел научиться программировать такие светодиодные конструкции.

                                          А если более серьезно, этот модуль не имеет цены, это ручная и абсолютно уникальная работа это воплощение мысли в технологиях века. Цель вовсе не в том что бы получить устройство, а в том чтобы получить результат работы.
                                        • 0

                                          Однажды подарил младшему брату какой-то starter kit Arduino, и там была светодиодная матрица, с которой я поигрался. В итоге получилось что-то такое (извиняйте, что не на гитхабе). JsusDev, посмотрите, если интересно. Может найдете что-то полезное для себя.


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


                                          • Код под ATmega 2560, отличается отображение регистров на пины ардуины и, возможно, инициализация таймера
                                          • Также у 2560 меги куча ОЗУ, и поэтому я забыл указать, чтобы хранение символов было во флеше. Напрямую в 328 не влезет. Прочитать про это: 1, 2
                                          • 0
                                            Спасибо, принял к сведенью обязательно изучу.
                                          • 0
                                            А зачем тут вообще регистр сдвига? Даже при использовании кварца имеем 18 свободных линий GPIO (23 всего — 2 XTAL — 2 UART — 1 RESET = 18), а ваша матрица 5x7 требует 12 линий.
                                            • 0
                                              По-моему, эта статья туториал в духе «дети, существует сдвиговый регистр. Его используют, когда линий не хватает». А то правда есть люди, которые вместо Uno берут Mega, потому что линии на подключение диодов кончились.
                                              • 0
                                                Тогда стоило бы использовать кристалл, у которого реально выходов не хватает, а то каким-то велосипедостроением попахивает. Да и вид у конструкции слижком уж утилитарный для туториала…
                                                • 0
                                                  По фен-шую много стоило бы сделать иначе, от платы до кода, но и так неплохо. Тем более что читатели хабра быстро понимают, что в комментах не меньше информации, чем в самой статье. Вообще, под статьёй можно найти годную эррату или пару полноценных бранчей.
                                              • 0
                                                Вы заметили что на плате размещено по мимо микросхем 18 контактов гребенки? Да вы можете спросить если так то почему тогда ты не используешь их? они что для красоты ?! нет они предполагаются для соединения оставшихся пинов ATmega328p.
                                              • 0

                                                Отличная статья. Сообщение при старте ок)

                                                • 0
                                                  Отличная статья для начинающих радиолюбителей. Автор показал не оптимальную реализацию, а варианты использования различных радиокомпонентов с подробным объяснением. Как раз подробного (и наглядного в данном случае) объяснения и не хватает некоторым авторам, которые считают себя очень крутыми и пишут соответственно, наверное, тоже для крутых. Побольше таких статей!..

                                                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                                  Самое читаемое