Это устройство может стать приятным подарком или украшением полки любого человека.
Компоненты
Приступим. Первое, что нужно для разработки любого устройства – это, как минимум, подготовить все необходимые радиокомпоненты или хотя бы основные составляющие.
- Резисторы 150 ом 0.25 Ватт — 7 шт.
- Конденсаторы 50 вольт 1 микрофарад — 5 шт.
- Кварцевый резонатор 16 мгц — 1 шт.
- Разъём типа гребёнка ( потребуется 18 контактов ) — 1 шт.
- Сдвиговый регистр 74ch595 корпус DIP — 1 шт.
- Панель под микросхему 74ch595 корпус DIP (16 ножек) — 1 шт.
- Светодиоды 5мм 3 вольта — 35 шт.
- Микроконтроллер ATmega328p корпус DIP — 1 шт.
- Панель под микросхему ATmega328p корпус DIP (28 ножек) — 1 шт.
- Монтажная плата 5x7 — 1 шт.
Рекомендую при необходимости купить флюс, припой и паяльник.
Я намеренно не указываю марку проводов, которая вам подойдет, так как совсем не владею информацией об их параметрах. Могу посоветовать МГТФ, вполне возможно, что подойдут. Если вы знаете, какие провода точно оптимальны, оставьте информацию в комментариях или напишите мне в личные сообщения jsusdev.
Я не буду вам советовать какие-либо конкретные интернет магазины, потому что это было давно
Сдвиговый регистр 74ch595
Наверное, многим новичкам станет не по себе от понимания принципов работы микросхемы 74ch595 вне этой статьи. Сейчас я попробую максимально доступно объяснить, как она работает и чем будет полезна в конкретном случае с моей LED-матрицей.
Проще говоря, микросхема предназначена для увеличения количества цифровых пинов.
Распиновка. Внимание! Рисунок имеет незначительные неточности в маркировке контактов, это сделано для более простого усвоения и понимания работы.
Самые загадочные контакты управления, которые вызывают интерес:
- output pin * — контакты вывода
- DS — (Serial Data Input) контакт который определяет состояние напряжения на контактах вывода
- SH — (Shift Register Clock Input) контакт который записывает состояние которое определенно в DS
- ST — (Storage Register Clock Input) контакт который открывает микросхему для записи и закрывает, устанавливая на контакты вывода нужные состояния определенные DS
Уверен, визуальный пример поможет вам осознать происходящее лучше.
Если нет, то я оставил и интерактивную версию: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); // конец записи в байт
}
Теперь, когда вы овладели работой с микросхемой, можно приступить к следующему пункту.
Подготовка к разработке матрицы
В моем случае светодиоды мешали друг другу на плате, и чтобы светодиоды «встали» на плату необходимо обточить мелкую окантовку, если конечно это требуется. Внимание! Нужно именно обточить, потому что есть еще немало способов от нее избавиться. Так как светодиоды на самом деле очень хрупкие, желательно обрабатывать их аккуратно, процесс выпаивания в случае чего мне ни разу не давался легко.
Хотелось бы обратить особое внимание на конденсаторы, я не могу объяснить их необходимость специальным языком, но без них светодиоды не затухают до конца и от этого устройство выглядит неоптимизированным, конденсаторы стабилизируют ток между контроллером и светодиодом.
В финальном исполнении установить конденсаторы получилось только так, хотя, раскрою секрет, подобное устройство было не одно, на одном из них конденсаторы крепились с обратной стороны на одну ножку в том месте, где сейчас проходит провод питания к 74ch595.
Не стоит беспокоиться о количестве проводов на фото, далее покажу как все устроено.
Совсем забыл, и вы, наверное, упустили из внимания, что на устройстве визуально расположено всего 6 резисторов, но на самом деле их 7: один из них находится прямо под 74ch595.
Стоит добавить, что кварцевый генератор (16 МГц) можно заменить на 24 МГц, это существенно ускорит работу, но такие изменения применимы только к готовому устройству, так как ATmega328p просто не прошивается на кварцевом генераторе 24 МГц.
Разработка матрицы
Принципиальная схема устройства.
Внимание! Светодиоды расположены «минусом» к ATmega328p и в тоже время «плюсом» к 74ch595. Расположение компонентов на плате.
Обратная часть (вариант без конденсаторов)
3D-модель, которую можно повертеть в редакторе Google SketchUp.
Саму модель можно скачать тут: 3D_Schema.skp
Когда мы запаяли все компоненты, можно припаивать провода.
Стартуем:
Соединяем контакты rx, tx, reset и светодиоды. (светодиоды соединены отрезанными от них ножками):
Соединяем компоненты питание и кварцевый генератор:
Соединяем светодиоды с 74ch595:
Соединяем светодиоды с ATmega328p и ATmega328p и 74ch595:
Соединяем конденсаторы:
Поздравляю физическая часть закончена:
Наверняка устройство сразу не оживет без прошивки, но можно попробовать стандартный blink Arduino. (Да, в некоторых случаях действительно может заработать).
У меня Blink заработал. Если у вас хоть что-то на первом ряду будет мигать значит все ок, если нет – читаем дальше.
Разработка программы
Чтобы микроконтроллер управлял своими контактами, их надо «инициализировать».
Да, я мог сделать иначе, например, использовать массив и цикл, чтобы сократить количество кода в функции. Вообще, множество программных решений, которые я принял в процессе реализации, губительны с какой-то позиции, например, для программируемой памяти (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);
Нумерация колонок идет справа налево, то есть зеркально массиву.
Что это за магические цифры в массиве?
Проще говоря, колонка светодиодов – это байт — 1 бит, вмещающий число в десятичной системе счисления от 0 до 127, этим числом мы определяем, каким светодиодам нужно зажечься.
Конечно, мы не будем собирать свои символы путем грубого или осознанного перебора бит в байте. Я разработал страницу, на которой можно сформировать любой символ Symbol creater.
Все предельно просто и легко, мы просто выбираем курсором нужны светодиоды и получаем массив, который можно использовать в качестве символа.
Функция, в которой определены символы. Изначально я планировал, что функцию можно будет совершенствовать, расширяя количество символов. Сейчас их 87, но я предполагал, что их может быть до 255. Но увы, наверное, в этом релизе нет, так как интерпретатор Arduino не определяет символы, код которых выше чем 127.
Функция, которая конвертирует строку в набор перебираемых символов, принимает два параметра:
- саму строку «Hellow world!»
- время между переключением символов в мс.
Функция которая конвертирует строку в набор перебираемых символов. Принимает два параметра это саму строку «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)
JsusDev Автор
20.04.2018 15:11Обойтись то можно и регистрами если палатой управлять извне, но думаю что вы понимаете что те провода которые идут извне это — питание и прошивка все управление происходит на контроллером на плате.
Alozar
20.04.2018 15:21Сколько же нервов пьёт подобное расположение выходов (я про питание и землю) при разводке односторонней платы.
На логической схеме всё хорошо и находится с одной стороны, а на деле приходится танцевать с бубном…GennPen
20.04.2018 16:20Одна перемычка под микросхемой, которую и вовсе не видно если смотреть с эстетической стороны.
Mike_soft
20.04.2018 16:13+1Светодиоды есть и в цилиндрических корпусах (в т.ч. и с плоским торцом), и в «квадратном» корпусе. в чем сакральный смысл «обточки»?
JsusDev Автор
20.04.2018 16:37Я отталкиваюсь о того что было у меня, у меня был только этот тип светодиодов, поэтому чтоб все светодиоды были равны и не было никаких смещений пришлось обточить.
Nibi
20.04.2018 16:38+2Электролитические конденсаторы прямо на портах МК? Зачем так издеваться над МК? Это же большой импульсный ток, рано или поздно пины повыгорают.
Gryphon88
20.04.2018 17:03Спасибо за наглядный туториал.
Вы в SketchUp модель выгружали или прям в нём делали? Люблю его за простоту, корпуса в нём прикидывать хорошо, но вот рисовать плату с нуля (без готовых компонентов) я ленился.
И вопрос к знатокам Arduino: код вида
преобразуется в маски по числу задействованных регистров, или по-пионерски — сколько строк, столько отдельных команд?digitalWrite(9, 1); digitalWrite(10, 1); digitalWrite(11, 1); digitalWrite(12, 1); digitalWrite(13, 1);
JsusDev Автор
20.04.2018 17:14Почти все компоненты рисовал прямо в SketchUp, кроме платы ее просто наложил на блок как текстуру.
GarryC
20.04.2018 17:26«А вы то сами как думаете?»
Если хотите по-взрослому, то Александреску Вам в помощь, а в самой Ардуино все по-пионерски.Gryphon88
20.04.2018 17:33Сурово. Я думал, что за столько-то лет разработки базовые функции оптимизировали.
Александреску я не осилил толком, но такие вещи и на макросах можно разматывать.Alex_ME
21.04.2018 12:19Хорошая статья на тему: Работа с портами ввода-вывода микроконтроллеров на Си++.
С Variadic Templates всё становитсябез черной магиис меньшим количеством черной магии.
Может быть, еще можно через C++14 constexpr сделать, циклами. Тогда будет совсем без магии. Хотя, там вроде функции без побочных эффектов должны быть? Я еще в этой теме не совсем разобрался.
Gryphon88
21.04.2018 15:10Читал, спасибо. Я такой код прочесть и понять смогу, у меня с написанием кода на шаблонах большие нелады.
Alex_ME
21.04.2018 15:41Кстати, кто-нибудь знает способ, как убедится, что constexpr-функция действительно constexpr? Я пытался смотреть дизассемблерный код, но там компилятор (ARM Compiler 6) и так инлайнит. А без оптимизации он вроде и constexpr отказывается делать… Может, ошибаюсь.
FGV
20.04.2018 17:24вам порты меги не жалко?
1) при зажигании 7 светодиодов нагрузка на порт будет = 7*(5-2)/150 = 140мА (по даташиту если память не изменяет память на пин порта около 20мА максимум);
2) акромя вкорячивания 140мА в светодиоды бонусом еще надо заряжать электролиты, т.е. еще дополнительная токовая нагрузка.GarryC
20.04.2018 17:28Так я понял смысл электролитов как раз в том, чтобы обеспечить пиковый ток при включенных светодиодах, а потом во время паузы их подзарядить. Но последовательные резисторы по любому нужны.
FGV
20.04.2018 17:43хм. динамическая индикация вроде как, тут электролиты как раз мешают. И на верхнее плечо лучше поставить таки нормальные транзисторные ключи (это убережет мегу от токового изнасилования).
виной тому кстати криво написаный код + адурина, как сделано:
1) гасим индикатор (выкл. все линии)
2) вдвигаем данные в регистр
3) щелкаем регистром (введенные данные появляются на выходе)
4) включаем линию.
п.2 — самые затратный по времени, по идее надо так:
1) вдвигаем данные в регистр
2) гасим индикатор (выкл. все линии)
3) щелкаем регистром (введенные данные появляются на выходе)
4) включаем линию.mickvav
20.04.2018 18:08Ну, если автор маленько перегрел конденсаторы при пайке или взял БУ-ные, то всё не так страшно. Да и с другой стороны диодов у него сдвиговый регистр — там тоже немного просядет (хотя микроконтроллер сдохнет первым, конечно).
JsusDev Автор
20.04.2018 23:52Я наверно не столь компетентен как вы, но основываясь на моем опыте который показывает что не один из контроллеров не потерял свои свойства пока я его использовал во всех позах я могу сделать вывод что если выводы и погорят то это будет еще очень не скоро.
Да я могу согласиться что было бы просто великолепно если бы контроллер управлял транизисторами, а они в свою очередь светодиодами — это было бы гуманней по отношению к контроллеру, поэтому принял к сведению постараюсь больше не косячить.GarryC
23.04.2018 11:35Дело не в компетенции, просто есть вещи, которые следует делать не задумываясь — ставить ограничивающие ток резисторы или умощняющие транзисторы. И только хорошенько подумав и убедившись, что все правильно получается, можно их не ставить. Ну а аргумент «ведь работает же» — это не для инженерного дела (надеюсь, мы именно им занимаемся).
Mike_soft
23.04.2018 11:57в журнале Микропроцессорные средства и системы году в 1985 был шутливый тест «являетесь ли вы инженером» (у них вообще на последнем развороте был раздел юмора). ну и там вопрос типа:
«каково напряжение питания микросхемы ***» с вариантами ответов
1)«5 вольт»
2)«9 вольт»
3)«я подал 12, и до сих пор работает»…
GarryC
20.04.2018 17:31Вообще то, после таких программ (смотри листинги в посте) я начинаю понимать Ардуино-хэйтеров, хотя сам к ним не отношусь.
ionicman
20.04.2018 17:54А чего не готовый модуль с Али за 1.7$?
JsusDev Автор
21.04.2018 00:02Я не планирую производить такие модули в больших объемах, вообще это устройство насколько я помню родилось только потому что я хотел научиться программировать такие светодиодные конструкции.
А если более серьезно, этот модуль не имеет цены, это ручная и абсолютно уникальная работа это воплощение мысли в технологиях века. Цель вовсе не в том что бы получить устройство, а в том чтобы получить результат работы.
Alex_ME
20.04.2018 18:05Однажды подарил младшему брату какой-то starter kit Arduino, и там была светодиодная матрица, с которой я поигрался. В итоге получилось что-то такое (извиняйте, что не на гитхабе). JsusDev, посмотрите, если интересно. Может найдете что-то полезное для себя.
Запись паттерна осуществляется в буфер, который отображается на матрице с помощью динамической индикации на таймере и не требует никаких дополнительных телодвижений в основной программе, помимо вкл/выкл светодиодов или записи буфера. Также сверху прикрутил возможность плавного сдвига одного паттерна на другой, а уже на этом реализовал бегущую строку.
quwy
21.04.2018 01:38А зачем тут вообще регистр сдвига? Даже при использовании кварца имеем 18 свободных линий GPIO (23 всего — 2 XTAL — 2 UART — 1 RESET = 18), а ваша матрица 5x7 требует 12 линий.
Gryphon88
21.04.2018 02:19По-моему, эта статья туториал в духе «дети, существует сдвиговый регистр. Его используют, когда линий не хватает». А то правда есть люди, которые вместо Uno берут Mega, потому что линии на подключение диодов кончились.
quwy
21.04.2018 03:41Тогда стоило бы использовать кристалл, у которого реально выходов не хватает, а то каким-то велосипедостроением попахивает. Да и вид у конструкции слижком уж утилитарный для туториала…
Gryphon88
23.04.2018 01:46По фен-шую много стоило бы сделать иначе, от платы до кода, но и так неплохо. Тем более что читатели хабра быстро понимают, что в комментах не меньше информации, чем в самой статье. Вообще, под статьёй можно найти годную эррату или пару полноценных бранчей.
JsusDev Автор
21.04.2018 05:01Вы заметили что на плате размещено по мимо микросхем 18 контактов гребенки? Да вы можете спросить если так то почему тогда ты не используешь их? они что для красоты ?! нет они предполагаются для соединения оставшихся пинов ATmega328p.
Dmitriy62
21.04.2018 13:08Отличная статья для начинающих радиолюбителей. Автор показал не оптимальную реализацию, а варианты использования различных радиокомпонентов с подробным объяснением. Как раз подробного (и наглядного в данном случае) объяснения и не хватает некоторым авторам, которые считают себя очень крутыми и пишут соответственно, наверное, тоже для крутых. Побольше таких статей!..
tretyakovmax
Когда я первый раз после многолетнего перерыва взял в руки паяльник и спаял себе на макетке программатор для AVR и он даже заработал у меня был аналогичный восторг) До того приходилось иметь дело только со всякой микросхемной рассыпухой в радиокружке, а тут вдруг оказалось что технологии ого-го как шагнули далеко вперед за время моего перерыва в радиолюбительстве.
А если по делу, то контроллер тут не нужен, двух регистров достаточно.