Как подключить LED-панель 95x8 к Arduino
Для проекта нам понадобятся:
- светодиодная модульная панель;
- Arduino UNO или иная совместимая плата;
- соединительные провода (например, вот такой набор);
- персональный компьютер со средой разработки Arduino IDE.
1Описание и подключение светодиодной панели
Светодиодные панели подобного типа часто используются в рекламе, в магазинах, в транспорте и других местах, где не требуется выводить большие объёмы текстовой или графической информации. Часто с помощью подобных панелей текст выводится по принципу бегущей строки. Данные панели состоят из светодиодных модулей различной размерности. Это могут быть модули 8 на 8 «точек», 8 на 5 и другие. Модули соединяются между собой, образуя большие панели («матрицы»). В рассматриваемом случае один модуль имеет 8 светодиодов по высоте и 5 по ширине, и эти модули в количестве 19 штук располагаются на единой печатной плате друг за другом. Иногда модули могут располагаться в форме, близкой к квадрату.
Вся электронная «обвязка» LED матрицы расположена с обратной стороны платы. На печатной плате имеется маркировка "DISP95.PCB BY ZHOU 2004/02/28".
Для управления панелью применяется последовательный интерфейс, схожий с SPI. Сигналы управления поступают через разъём JP2, который представляет собой двухрядную «гребёнку» коннекторов типа PLD-40. Рядом с разъёмом JP2 находятся клеммы подключения питания. К ним припаяны два провода: жёлтый – GND, синий – VCC (+5 В).
Прежде чем подавать питание на LED панель, необходимо убедиться, что у источника питания хватит мощности, т.к. матрица потребляет довольно много. Один модуль может потреблять до 300 мА; умножаем на число модулей и получаем весьма приличный ток и, соответственно, потребляемую мощность.
Для того чтобы управлять таким большим числом светодиодов (19*5*8=760), разумеется, нужно иметь сдвиговый регистр. Что мы и наблюдаем: данная LED панель использует 12 сдвиговых регистров SM74HC595D, расположенных на её обратной стороне.
Сдвиговый регистр SM74HC595В – это китайский аналог американской микросхемы SN74HC595, который часто используется как драйвер для управления разнообразными дисплеями (LED, LCD и др.). В конце статьи можно скачать техническое описание на сдвиговый регистр SM74HC595, а также на оригинальную микросхему SN74HC595.
Ниже представлена таблица подключений светодиодной панели к Arduino. Здесь в скобках для LED панели показаны названия, соответствующие управляющим выводам сдвигового регистра, потому что именно он, по сути, и является управляющим элементом. В скобках для Arduino показаны названия выводов, которые относятся к режиму SPI.
Вывод разъёма JP2 на LED панели | Вывод Arduino |
---|---|
A (ADDR0) | D7 |
B (ADDR1) | D6 |
C (ADDR2) | D5 |
D (ENA) | D4 |
STB (RCLK) | D10 (SS) |
CLK (SRCLK) | D13 (SCK) |
DATA (SER) | D11 (MOSI) |
GND | GND |
Я не говорю, что это единственно возможный способ подключения светодиодной панели. Но если изучить техническое описание микросхемы HC595, то станет понятно, что для работы с выводами STB, CLK и DATA удобно использовать аппаратные ножки Arduino режима SPI (SS, SCK, MOSI). А для работы с выводами адресации (ADDR0..ADDR2) и разрешения записи (ENA) световой панели, в принципе, можно использовать любые цифровые пины Arduino.
2Управление LED панелью с помощью Arduino
Первый скетч будет базовым и довольно простым. Мы будем поочерёдно зажигать горизонтальные строки нашей LED панели.
Скетч для вывода линий на LED-панель
#include <SPI.h> // задаём назначение выводов Arduino: const byte A = 7; const byte B = 6; const byte C = 5; const byte D = 4; const byte STB = 10; const byte CLK = 13; const byte DATA = 11; void setup() { // назначаем режимы работы выводов: pinMode(A, OUTPUT); pinMode(B, OUTPUT); pinMode(C, OUTPUT); pinMode(D, OUTPUT); pinMode(STB, OUTPUT); pinMode(CLK, OUTPUT); pinMode(DATA, OUTPUT); // выставляем начальные значения: digitalWrite(A, LOW); digitalWrite(B, LOW); digitalWrite(C, LOW); digitalWrite(D, LOW); // разрешаем вывод на LED панель digitalWrite(STB, HIGH); // инициализируем режим SPI: SPI.begin(); // гасим все светодиоды: byte arrBlank[] = {255,255,255,255,255,255,255,255,255,255,255,255}; for (int row=0; row<8; row++) { writeLine(arrBlank); } delay(100); } byte arr[] = {0,0,0,0,0,0,0,0,0,0,0,0}; // 12 байтов = 12*8 бит = 96 бит - это одна строка LED панели (+1 лишний бит) void loop() { for (int row=0; row<8; row++) { setLine(row); // задаём номер строки LED матрицы writeLine(arr); // записываем в строку массив из 96 бит delay(100); // делаем задержку и повторяем } } // записывает 96 битов строки void writeLine(byte *ar) { digitalWrite(STB, LOW); // разрешаем передачу по SPI delay(1); for (int i=0; i<12; i++){ SPI.transfer(ar[i]); // передаём i-ый байт массива } digitalWrite(STB, HIGH); // завершаем передачу по SPI delay(1); } // задаёт номер строки, от 0 до 7 void setLine(byte lineNumber) { switch (lineNumber) { case 0: setAddr(0, 0, 0); break; case 1: setAddr(1, 0, 0); break; case 2: setAddr(0, 1, 0); break; case 3: setAddr(1, 1, 0); break; case 4: setAddr(0, 0, 1); break; case 5: setAddr(1, 0, 1); break; case 6: setAddr(0, 1, 1); break; case 7: setAddr(1, 1, 1); break; } } // выставляет адрес на пинах адреса: void setAddr(bool levA, bool levB, bool levC) { digitalWrite(A, levA); digitalWrite(B, levB); digitalWrite(C, levC); }
После загрузки и выполнения скетча получится что-то типа этого:
В динамике работа скетча со светодиодной панелью выглядит так:
Ключевые моменты в скетче следующие:
- запись данных происходит построчно;
- чтобы записать одну строку из 95-ти точек нужно передать в сдвиговый регистр 95-битовое число или, как сделано в скетче, массив;
- чтобы выбрать номер строки, с помощью пинов ADDR0..ADDR2 следует выставить её адрес (пины ADDRx образуют 3 бита: 000 – 1-ая строка, 001 – 2-ая, 010 – 3-я, 011 – 4-ая и т.п.);
- бит "1" гасит ячейку в строке, бит "0" – зажигает (например, передавая число 0x0F мы погасим 4 бита этого байта, а 4 бита зажгём).
Если убрать задержку 100 мс в основном цикле loop, то панель будет светиться вся, т.к. наше инерционное зрение не способно различать такие быстрые переключения строк. На этом основывается и работа мониторов и других дисплеев, в которых используется построчная развёртка кадров. При такой развёртке в каждый момент времени на экране отрисовывается только одна строка, а глаз хранит образ предыдущих строк, и в мозгу достраивается вся картинка.
3Вывод статического текста и изображенияна LED панель с помощью Arduino
Принцип работы с LED панелью заключается в том, что нужно зажигать c определённой частотой определённые светодиоды, чтобы вывести на матрицу изображение или текст. Ведь по сути, матрица – это дисплей с разрешением 8 на 95 точек. В следующем скетче выведем на матрицу неподвижный текст. Например, такой:
То, что на рисунке показано синими линиями пусть будут горящие светодиоды. Каждый светодиод будем описывать битом, равным "1", а каждый потухший – битом "0". Таким образом, каждая из 8-ми строк будет представлена массивом из 12-ти байтов. Например, первую строку будет описывать такой массив:
{B01110111, B00000010, B01000000, B00000001, B11000000, B01000100, B00000000, B00000000, B00000000, B00000100, B00000000, B00000000};
В языке программирования для Arduino двоичные числа можно записывать таким образом: B11110000, что соответствует шестнадцатеричному 0xF0 или десятичному 240. Записывая число в двоичной нотации, становятся видны отдельные биты. Через это можно представить, как будет выглядеть рисунок в данной строке LED матрицы.
Красными вертикальными линиями для наглядности отделены друг от друга зоны, которые будут представлены отдельными байтами.
Алгоритм вывода текста (или изображения) предельно прост:
- перейти к нужной строке LED матрицы (выставить адрес строки);
- записать массив из 12-ти байтов (96 бит) в сдвиговый регистр.
Также немного переделаем функцию writeLine(). Пусть она принимает в качестве аргументов номер строки и описывающий её массив. Перенести номер строки в эту функцию – логичнее и просто выглядит красивее. Кроме того, будем записывать в сдвиговый регистр значение, объединённое по XOR (^) с числом 0xFF, то есть, инвертированное число. Это необходимо для того, чтобы описывать в массиве горящие точки битами "1", а не "0". Если бы мы передавали не инвертированное число, то на панели появилось бы инвертированное изображение.
Соединяя описанное, получим такой скетч:
Скетч для вывода статического текста на LED-панель
#include <SPI.h> const byte A = 7; const byte B = 6; const byte C = 5; const byte ENA = 4; // => D const byte STB = 10; const byte CLK = 13; const byte DATA = 11; void setup() { pinMode(A, OUTPUT); pinMode(B, OUTPUT); pinMode(C, OUTPUT); pinMode(ENA, OUTPUT); pinMode(STB, OUTPUT); pinMode(CLK, OUTPUT); pinMode(DATA, OUTPUT); digitalWrite(A, LOW); digitalWrite(B, LOW); digitalWrite(C, LOW); digitalWrite(ENA, HIGH); SPI.begin(); delay(100); } // задаём массив, который описывает изображение на LED панели: byte buffer[8][12] = { { B01110111, B00000010, B01000000, B00000001, B11000000, B01000100, B00000000, B00000000, B00000000, B00000100, B00000000, B00000000 }, { B00100010, B00000010, B01000000, B00000010, B00100000, B01000100, B00000000, B00000000, B00000000, B00000100, B00001000, B00000000 }, { B00100010, B00000010, B01000000, B00000010, B00000000, B01001111, B00000000, B00000000, B00000000, B00000100, B00000100, B00000000 }, { B00111110, B00110010, B01000110, B00000001, B11000110, B01000100, B00110010, B01000000, B01100100, B10000100, B01100010, B00000000 }, { B00100010, B01001010, B01001001, B00000000, B00101001, B01000100, B00001010, B01000000, B10010100, B10000100, B00000010, B00000000 }, { B00100010, B01111010, B01001001, B00000010, B00101001, B01000100, B00111010, B01000000, B10000100, B10000000, B01100010, B00000000 }, { B00100010, B01000010, B01001001, B00110010, B00101001, B01000100, B01001010, B01001100, B10000100, B10000100, B00000100, B00000000 }, { B01110111, B00110011, B01100110, B00100001, B11000110, B01100011, B00111001, B11001100, B10000011, B10001110, B00001000, B00000000 } }; void loop() { // построчно выводим изображение: for (int i = 0; i < 8; i++) { writeLine(i, buffer[i]); } } // записывает 96 битов строки void writeLine(byte line, byte *ar) { setLine(line); digitalWrite(STB, LOW); for (int i=0; i<12; i++){ SPI.transfer(ar[i]^0xff); } digitalWrite(STB, HIGH); } // задаёт номер строки, от 0 до 7 void setLine(byte lineNumber) { digitalWrite(ENA, LOW); delay(1); switch (lineNumber){ case 0: setAddr(0, 0, 0); break; case 1: setAddr(1, 0, 0); break; case 2: setAddr(0, 1, 0); break; case 3: setAddr(1, 1, 0); break; case 4: setAddr(0, 0, 1); break; case 5: setAddr(1, 0, 1); break; case 6: setAddr(0, 1, 1); break; case 7: setAddr(1, 1, 1); break; } digitalWrite(ENA, HIGH); } // выставляет на входах адреса число: void setAddr(bool levA, bool levB, bool levC) { digitalWrite(A, levA); digitalWrite(B, levB); digitalWrite(C, levC); }
В результате выполнения этого скетча получится вот такое изображение:
Конечно, всё это довольно занятно, но чаще всего такого рода панели используют для вывода текста в виде бегущей строки. Кроме того, довольно утомительно для каждого нового рисунка или надписи прописывать все 760 точек (95*8) матрицы вручную. Хотелось бы иметь какой-то механизм, который обеспечивал бы вывод текста в произвольном месте матрицы и автоматическое его перемещение. Этим и займёмся далее. Но сначала давайте отобразим на панели бегущую слева направо картинку.
3Вывод динамического изображения на LED панель с помощью Arduino
Алгоритм следующий. Мы должны каждую из 8-ми строк панели на каждом цикле сдвигать влево на 1 столбец. Для хранения данных у нас так же есть двумерный буфер. Мы в цикле будем сдвигать буфер влево, а затем отрисовывать его содержимое на LED панели. Скорость бегущей картинки будем регулировать циклами пропуска сдвига, т.е. будем сдвигать буфер, скажем, только каждый 100-ый раз цикла (переменная RPT). Кроме того, введём некоторую универсальность путём добавления констант, задающих число сдвиговых регистров (NUM_DEVICES) и строк матрицы (NUM_LINES).
При сдвиге массива мы будем сдвигать каждый байт буфера влево на 1, при этом должны запоминать старший бит следующего за ним байта, и выставлять его в качестве младшего бита сдвигаемого байта. Таким образом мы обеспечим сохранение картинки и при переполнении биты картинки не будут теряться, а будут перемещаться от байта к байту.
Выбор строки панели также упростим, поместив в функцию SetLine().
Скетч для вывода бегущей картинки на LED-панель (разворачивается)
#include <SPI.h> #define NUM_LINES 8 // число строк LED панели #define NUM_DEVICES 12 // число сдвиговых регистров #define RPT 100 // регулирует скорость бегущей строки const byte A = 7; const byte B = 6; const byte C = 5; const byte ENA = 4; // => D const byte STB = 10; const byte CLK = 13; const byte DATA = 11; byte buffer[NUM_LINES][NUM_DEVICES] = { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 } }; // картинка «стрелка влево» void setup() { InitLedPanel(); } // Задаёт назначение выводов, инициализирует SPI и последовательный порт. void InitLedPanel() { pinMode(A, OUTPUT); pinMode(B, OUTPUT); pinMode(C, OUTPUT); pinMode(ENA, OUTPUT); pinMode(STB, OUTPUT); pinMode(CLK, OUTPUT); pinMode(DATA, OUTPUT); digitalWrite(A, LOW); digitalWrite(B, LOW); digitalWrite(C, LOW); digitalWrite(ENA, HIGH); digitalWrite(STB, HIGH); SPI.begin(); Serial.begin(115200); } void loop() { ShiftBuffer(); for (int i = 0; i < RPT; i++) { // несколько раз показываем тот же буфер DisplayBuffer(); } } // Сдвигает содержимое буфера на 1 столбец влево. void ShiftBuffer() { for (int l = 0; l < NUM_LINES; l++) { byte msb = 0; // старший бит числа for (int d = 0; d < NUM_DEVICES; d++) { if (d < NUM_DEVICES - 1) { msb = buffer[l][d + 1] >> 7; // запомнили старший бит следующего числа } else { msb = 0; } buffer[l][d] = (buffer[l][d] << 1) | msb; // сдвигаем на 1 бит и младший заполняем значением предыдущего старшего } } Serial.println("Shifted:"); for (int l = 0; l < NUM_LINES; l++) { for (int d = 0; d < NUM_DEVICES; d++) { Serial.print(buffer[l][d], HEX); Serial.print(" "); } Serial.println(); } } // Выводит текущий буфер на LED панель. void DisplayBuffer() { for (int i = 0; i < NUM_LINES; i++) { WriteLine(i, buffer[i]); } } // Выводит на LED панель заданную строку. void WriteLine(int line, byte *ar) { SetLine(line); digitalWrite(STB, LOW); for (int i = 0; i < NUM_DEVICES; i++) { SPI.transfer(ar[i] ^ 0xff); // ^0xFF = инверсия изображения } digitalWrite(STB, HIGH); } // Выбирает строку LED панели по номеру. void SetLine(int lineNumber) { digitalWrite(ENA, LOW); delay(1); // Выставляет номер строки LED панели: digitalWrite(A, lineNumber & 1); // выставляем младший бит digitalWrite(B, (lineNumber >> 1) & 1); // выставляем средний бит digitalWrite(C, (lineNumber >> 2) & 1); // выставляем старший бит digitalWrite(ENA, HIGH); }
Ну вот, уже гораздо ближе к тому, что мы хотели.
4Вывод динамического текста на LED панель с помощью Arduino
Ну что ж, самое интересное. Будем выводить бегущую строку, а кроме того, добавим возможность из последовательного порта задавать текст.
Подробное объяснение кода и принципа работы скетча бегущей строки приводится в приложенном видео.
Скетч для вывода бегущей строки на LED-панель (разворачивается)
#include <SPI.h> #define SYM_WIDTH 6 // ширина символа #define NUM_LINES 8 // число строк LED панели #define NUM_DEVICES 12 // число сдвиговых регистров #define RPT 10 // регулирует скорость бегущей строки #define MAX_LEN 200 // максимальная длина сообщения const byte A = 7; const byte B = 6; const byte C = 5; const byte ENA = 4; // => D const byte STB = 10; const byte CLK = 13; const byte DATA = 11; char inputMessage[MAX_LEN]; // буфер для приёма сообщения из последовательного порта int curInputIndex = 0; // индекс текущего принятого символа // Массив ASCII символов (шрифт). byte characters[96][SYM_WIDTH] = { { 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000 }, // space { 0b00000000, 0b00000000, 0b01001111, 0b00000000, 0b00000000, 0b00000000 }, // ! { 0b00000000, 0b00000111, 0b00000000, 0b00000111, 0b00000000, 0b00000000 }, // " { 0b00010100, 0b01111111, 0b00010100, 0b01111111, 0b00010100, 0b00000000 }, // # { 0b00100100, 0b00101010, 0b01111111, 0b00101010, 0b00010010, 0b00000000 }, // $ { 0b00100011, 0b00010011, 0b00001000, 0b01100100, 0b01100010, 0b00000000 }, // % { 0b00110110, 0b01001001, 0b01010101, 0b00100010, 0b01010000, 0b00000000 }, // & { 0b00000000, 0b00000101, 0b00000011, 0b00000000, 0b00000000, 0b00000000 }, // ; { 0b00000000, 0b00011100, 0b00100010, 0b01000001, 0b00000000, 0b00000000 }, // ( { 0b00000000, 0b01000001, 0b00100010, 0b00011100, 0b00000000, 0b00000000 }, // ) { 0b00010100, 0b00001000, 0b00111110, 0b00001000, 0b00010100, 0b00000000 }, // * { 0b00001000, 0b00001000, 0b00111110, 0b00001000, 0b00001000, 0b00000000 }, // + { 0b00000000, 0b01010000, 0b00110000, 0b00000000, 0b00000000, 0b00000000 }, // , { 0b00001000, 0b00001000, 0b00001000, 0b00001000, 0b00001000, 0b00000000 }, // - { 0b00000000, 0b01100000, 0b01100000, 0b00000000, 0b00000000, 0b00000000 }, // . { 0b00100000, 0b00010000, 0b00001000, 0b00000100, 0b00000010, 0b00000000 }, // / { 0b00111110, 0b01010001, 0b01001001, 0b01000101, 0b00111110, 0b00000000 }, // 0 { 0b00000000, 0b01000010, 0b01111111, 0b01000000, 0b00000000, 0b00000000 }, // 1 { 0b01000010, 0b01100001, 0b01010001, 0b01001001, 0b01000110, 0b00000000 }, // 2 { 0b00100001, 0b01000001, 0b01000101, 0b01001011, 0b00110001, 0b00000000 }, // 3 { 0b00011000, 0b00010100, 0b00010010, 0b01111111, 0b00010000, 0b00000000 }, // 4 { 0b00100111, 0b01000101, 0b01000101, 0b01000101, 0b00111001, 0b00000000 }, // 5 { 0b00111100, 0b01001010, 0b01001001, 0b01001001, 0b00110000, 0b00000000 }, // 6 { 0b00000011, 0b01110001, 0b00001001, 0b00000101, 0b00000011, 0b00000000 }, // 7 { 0b00110110, 0b01001001, 0b01001001, 0b01001001, 0b00110110, 0b00000000 }, // 8 { 0b00000110, 0b01001001, 0b01001001, 0b00101001, 0b00011110, 0b00000000 }, // 9 { 0b00000000, 0b01101100, 0b01101100, 0b00000000, 0b00000000, 0b00000000 }, // : { 0b00000000, 0b01010110, 0b00110110, 0b00000000, 0b00000000, 0b00000000 }, // ; { 0b00001000, 0b00010100, 0b00100010, 0b01000001, 0b00000000, 0b00000000 }, // < { 0b00010100, 0b00010100, 0b00010100, 0b00010100, 0b00010100, 0b00000000 }, // = { 0b00000000, 0b01000001, 0b00100010, 0b00010100, 0b00001000, 0b00000000 }, // > { 0b00000010, 0b00000001, 0b01010001, 0b00001001, 0b00000110, 0b00000000 }, // ? { 0b00110010, 0b01001001, 0b01111001, 0b01000001, 0b00111110, 0b00000000 }, // @ { 0b01111110, 0b00010001, 0b00010001, 0b00010001, 0b01111110, 0b00000000 }, // A { 0b01111111, 0b01001001, 0b01001001, 0b01001001, 0b00111110, 0b00000000 }, // B { 0b00111110, 0b01000001, 0b01000001, 0b01000001, 0b00100010, 0b00000000 }, // C { 0b01111111, 0b01000001, 0b01000001, 0b01000001, 0b00111110, 0b00000000 }, // D { 0b01111111, 0b01001001, 0b01001001, 0b01001001, 0b01001001, 0b00000000 }, // E { 0b01111111, 0b00001001, 0b00001001, 0b00001001, 0b00000001, 0b00000000 }, // F { 0b00111110, 0b01000001, 0b01001001, 0b01001001, 0b00111010, 0b00000000 }, // G { 0b01111111, 0b00001000, 0b00001000, 0b00001000, 0b01111111, 0b00000000 }, // H { 0b01000001, 0b01000001, 0b01111111, 0b01000001, 0b01000001, 0b00000000 }, // I { 0b00110000, 0b01000001, 0b01000001, 0b00111111, 0b00000001, 0b00000000 }, // J { 0b01111111, 0b00001000, 0b00010100, 0b00100010, 0b01000001, 0b00000000 }, // K { 0b01111111, 0b01000000, 0b01000000, 0b01000000, 0b01000000, 0b00000000 }, // L { 0b01111111, 0b00000010, 0b00001100, 0b00000010, 0b01111111, 0b00000000 }, // M { 0b01111111, 0b00000100, 0b00001000, 0b00010000, 0b01111111, 0b00000000 }, // N { 0b00111110, 0b01000001, 0b01000001, 0b01000001, 0b00111110, 0b00000000 }, // O { 0b01111111, 0b00001001, 0b00001001, 0b00001001, 0b00000110, 0b00000000 }, // P { 0b00111110, 0b01000001, 0b01010001, 0b00100001, 0b01011110, 0b00000000 }, // Q { 0b01111111, 0b00001001, 0b00001001, 0b00011001, 0b01100110, 0b00000000 }, // R { 0b01000110, 0b01001001, 0b01001001, 0b01001001, 0b00110001, 0b00000000 }, // S { 0b00000001, 0b00000001, 0b01111111, 0b00000001, 0b00000001, 0b00000000 }, // T { 0b00111111, 0b01000000, 0b01000000, 0b01000000, 0b00111111, 0b00000000 }, // U { 0b00001111, 0b00110000, 0b01000000, 0b00110000, 0b00001111, 0b00000000 }, // V { 0b00111111, 0b01000000, 0b00111000, 0b01000000, 0b00111111, 0b00000000 }, // W { 0b01100011, 0b00010100, 0b00001000, 0b00010100, 0b01100011, 0b00000000 }, // X { 0b00000011, 0b00000100, 0b01111000, 0b00000100, 0b00000011, 0b00000000 }, // Y { 0b01100001, 0b01010001, 0b01001001, 0b01000101, 0b01000011, 0b00000000 }, // Z { 0b01111111, 0b01000001, 0b01000001, 0b00000000, 0b00000000, 0b00000000 }, // [ { 0b00000010, 0b00000100, 0b00001000, 0b00010000, 0b00100000, 0b00000000 }, // '\' { 0b00000000, 0b00000000, 0b01000001, 0b01000001, 0b01111111, 0b00000000 }, // ] { 0b00000100, 0b00000010, 0b00000001, 0b00000010, 0b00000100, 0b00000000 }, // ^ { 0b01000000, 0b01000000, 0b01000000, 0b01000000, 0b01000000, 0b00000000 }, // _ { 0b00000000, 0b00000001, 0b00000010, 0b00000100, 0b00000000, 0b00000000 }, // ` { 0b00100000, 0b01010100, 0b01010100, 0b01010100, 0b01111000, 0b00000000 }, // a { 0b01111111, 0b01001000, 0b01000100, 0b01000100, 0b00111000, 0b00000000 }, // 0b { 0b00111000, 0b01000100, 0b01000100, 0b01000100, 0b00100000, 0b00000000 }, // c { 0b00111000, 0b01000100, 0b01000100, 0b01001000, 0b01111111, 0b00000000 }, // d { 0b00111000, 0b01010100, 0b01010100, 0b01010100, 0b00011000, 0b00000000 }, // e { 0b00001000, 0b01111110, 0b00001001, 0b00000001, 0b00000010, 0b00000000 }, // f { 0b00001100, 0b01010010, 0b01010010, 0b01010010, 0b00111110, 0b00000000 }, // g { 0b01111111, 0b00001000, 0b00000100, 0b00000100, 0b01111000, 0b00000000 }, // h { 0b00000000, 0b01000100, 0b01111101, 0b01000000, 0b00000000, 0b00000000 }, // i { 0b00100000, 0b01000000, 0b01000100, 0b00111101, 0b00000000, 0b00000000 }, // j { 0b01111111, 0b00010000, 0b00101000, 0b01000100, 0b00000000, 0b00000000 }, // k { 0b00000000, 0b01000001, 0b01111111, 0b01000000, 0b00000000, 0b00000000 }, // l { 0b01111000, 0b00000100, 0b00001000, 0b00000100, 0b01111000, 0b00000000 }, // m { 0b01111100, 0b00001000, 0b00000100, 0b00000100, 0b01111000, 0b00000000 }, // n { 0b00111000, 0b01000100, 0b01000100, 0b01000100, 0b00111000, 0b00000000 }, // o { 0b01111100, 0b00010100, 0b00010100, 0b00010100, 0b00001000, 0b00000000 }, // p { 0b00001000, 0b00010100, 0b00010100, 0b01111100, 0b00000000, 0b00000000 }, // q { 0b01111100, 0b00001000, 0b00000100, 0b00000100, 0b00001000, 0b00000000 }, // r { 0b01001000, 0b01010100, 0b01010100, 0b01010100, 0b00100000, 0b00000000 }, // s { 0b00000100, 0b00111111, 0b01000100, 0b01000000, 0b00100000, 0b00000000 }, // t { 0b00111100, 0b01000000, 0b01000000, 0b00100000, 0b01111100, 0b00000000 }, // u { 0b00011100, 0b00100000, 0b01000000, 0b00100000, 0b00011100, 0b00000000 }, // v { 0b00111100, 0b01000000, 0b00110000, 0b01000000, 0b00111100, 0b00000000 }, // w { 0b01000100, 0b00101000, 0b00010000, 0b00101000, 0b01000100, 0b00000000 }, // x { 0b00001100, 0b01010000, 0b01010000, 0b01010000, 0b00111100, 0b00000000 }, // y { 0b01000100, 0b01100100, 0b01010100, 0b01001100, 0b01000100, 0b00000000 }, // z { 0b00000000, 0b00001000, 0b00110110, 0b01000001, 0b00000000, 0b00000000 }, // { { 0b00000000, 0b00000000, 0b01111111, 0b00000000, 0b00000000, 0b00000000 }, // | { 0b00000000, 0b01000001, 0b00110110, 0b00001000, 0b00000000, 0b00000000 }, // } { 0b00001000, 0b00000100, 0b00000100, 0b00001000, 0b00000100, 0b00000000 } // ~ }; // characters[95] char message[MAX_LEN] = "Hello, Soltau.ru! :)"; // отображаемое сообщение int messageLen = 21; // длина текущего сообщения byte buffer[NUM_LINES][NUM_DEVICES] = { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xf }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xf }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 } }; void setup() { InitLedPanel(); InitBuffer(); } // Задаёт назначение выводов, инициализирует SPI и последовательный порт. void InitLedPanel() { pinMode(A, OUTPUT); pinMode(B, OUTPUT); pinMode(C, OUTPUT); pinMode(ENA, OUTPUT); pinMode(STB, OUTPUT); pinMode(CLK, OUTPUT); pinMode(DATA, OUTPUT); digitalWrite(A, LOW); digitalWrite(B, LOW); digitalWrite(C, LOW); digitalWrite(ENA, HIGH); digitalWrite(STB, HIGH); SPI.begin(); Serial.begin(115200); } void loop() { for (int n = 0; n < messageLen; n++) { // для каждого символа сообщения int asciiVal = (int)message[n] - 32; // ASCII код символа; 32 - сдвиг индексов к нулю for (int col = 0; col < SYM_WIDTH; col++) { // для каждого столбца символа ShiftBuffer(); // сдвигаем буфер, освобождается крайний правый столбец byte symCol = characters[asciiVal][col]; // текущий столбец символа PushColumn(symCol); // размещаем столбец символа в крайнем правом столбце буфера // Несколько раз отображаем текущий буфер. // RPT – регулировка скорости бегущей строки: // чем больше RPT, тем ниже скорость перемещения бегущей строки. for (int r = 0; r < RPT; r++) { DisplayBuffer(); } } } } // Вдвигаем столбец символа в крайний правый столбец буфера. void PushColumn(byte col) { for (int i = 0; i < NUM_LINES; i++) { // каждый бит столбца размещается в своей строке byte curBit = (col >> i) & 1; // текущий бит столбца buffer[i][NUM_DEVICES - 1] = buffer[i][NUM_DEVICES - 1] | curBit; // размещаем в младшем разряде последнего байта буфера } } // Инициализирует пустой буфер. void InitBuffer(void) { for (int l = 0; l < NUM_LINES; l++) { for (int d = 0; d < NUM_DEVICES; d++) { buffer[l][d] = 0x00; } } Serial.println("Buffer initialized"); } // Сдвигает содержимое буфера на 1 столбец влево. void ShiftBuffer() { for (int l = 0; l < NUM_LINES; l++) { byte msb = 0; // старший бит числа for (int d = 0; d < NUM_DEVICES; d++) { if (d < NUM_DEVICES - 1) { msb = buffer[l][d + 1] >> 7; // запомнили старший бит следующего числа } else { msb = 0; } buffer[l][d] = (buffer[l][d] << 1) | msb; // сдвигаем на 1 бит и младший заполняем значением предыдущего старшего } } // Serial.println("Shifted:"); // Для отладочного вывода, если надо // for (int l = 0; l < NUM_LINES; l++) { // for (int d = 0; d < NUM_DEVICES; d++) { // Serial.print(buffer[l][d], HEX); // Serial.print(" "); // } // Serial.println(); // } } // Выводит текущий буфер на LED панель. void DisplayBuffer() { for (int i = 0; i < NUM_LINES; i++) { WriteLine(i, buffer[i]); } } // Выводит на LED панель заданную строку. void WriteLine(int line, byte *ar) { SetLine(line); digitalWrite(STB, LOW); for (int i = 0; i < NUM_DEVICES; i++) { SPI.transfer(ar[i] ^ 0xff); // ^0xFF = инверсия изображения } digitalWrite(STB, HIGH); } // Выбирает строку LED панели по номеру. void SetLine(int lineNumber) { digitalWrite(ENA, LOW); delay(1); // Выставляет номер строки LED панели: digitalWrite(A, lineNumber & 1); // выставляем младший бит digitalWrite(B, (lineNumber >> 1) & 1); // выставляем средний бит digitalWrite(C, (lineNumber >> 2) & 1); // выставляем старший бит digitalWrite(ENA, HIGH); } // Событие по приходу данных в последовательный порт. void serialEvent() { while (Serial.available()) { char inChar = (char)Serial.read(); // принимаем символ из порта inputMessage[curInputIndex] = inChar; // размещаем во временном буфере curInputIndex += 1; // запоминаем текущее положение в буфере if (inChar == '\n') { // если пришёл символ переноса строки InitBuffer(); for (int i = 0; i < curInputIndex; i++) { message[i] = inputMessage[i]; // обновляем выводимое сообщение } messageLen = curInputIndex; // запоминаем размер сообщения curInputIndex = 0; } } }
Видео с пояснениями к коду.
5Бегущая строка на Arduino с поддержкой кириллицы
Добавим в предыдущий скетч поддержку кириллицы. Это потребует некоторого усложнения. Добавим в массив шрифта кириллические символы (их можно сгенерировать с помощью приложенной программы или воспользоваться моим вариантом из скетча). Придётся конвертировать буквы из кодировки UTF-8 в Windows-1251, чтобы они занимали по прежнему 1 байт (UTF-8 занимает переменное число байт на один символ вне диапазона ASCII). Из-за этого придётся поменять тип буфера сообщения с char на int, и будем хранить в нём не сами символы сообщения, а их индексы в массиве шрифта. Индексы будем определять в момент получения данных из последовательного порта. От буквы «Ё» придётся отказаться ((
Скетч вывода бегущей строки с кириллицей (разворачивается)
#include <SPI.h> #define SYM_WIDTH 6 // ширина символа #define NUM_LINES 8 // число строк LED панели #define NUM_DEVICES 12 // число сдвиговых регистров #define RPT 10 // регулирует скорость бегущей строки #define MAX_LEN 100 // максимальная длина сообщения const byte A = 7; const byte B = 6; const byte C = 5; const byte ENA = 4; // => D const byte STB = 10; const byte CLK = 13; const byte DATA = 11; int inputIndeces[MAX_LEN]; // буфер для приёма индексов сообщения из последовательного порта int curInputIndex = 0; // индекс текущего принятого символа // Массив символов (шрифт) в кодировке windows-1251. byte characters[159][SYM_WIDTH] = { { 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000 }, // space, элемент 0, ASCII 32 => адрес 32-32 { 0b00000000, 0b00000000, 0b01001111, 0b00000000, 0b00000000, 0b00000000 }, // ! { 0b00000000, 0b00000111, 0b00000000, 0b00000111, 0b00000000, 0b00000000 }, // " { 0b00010100, 0b01111111, 0b00010100, 0b01111111, 0b00010100, 0b00000000 }, // # { 0b00100100, 0b00101010, 0b01111111, 0b00101010, 0b00010010, 0b00000000 }, // $ { 0b00100011, 0b00010011, 0b00001000, 0b01100100, 0b01100010, 0b00000000 }, // % { 0b00110110, 0b01001001, 0b01010101, 0b00100010, 0b01010000, 0b00000000 }, // & { 0b00000000, 0b00000101, 0b00000011, 0b00000000, 0b00000000, 0b00000000 }, // ; { 0b00000000, 0b00011100, 0b00100010, 0b01000001, 0b00000000, 0b00000000 }, // ( { 0b00000000, 0b01000001, 0b00100010, 0b00011100, 0b00000000, 0b00000000 }, // ) { 0b00010100, 0b00001000, 0b00111110, 0b00001000, 0b00010100, 0b00000000 }, // * // 10 { 0b00001000, 0b00001000, 0b00111110, 0b00001000, 0b00001000, 0b00000000 }, // + { 0b00000000, 0b01010000, 0b00110000, 0b00000000, 0b00000000, 0b00000000 }, // , { 0b00001000, 0b00001000, 0b00001000, 0b00001000, 0b00001000, 0b00000000 }, // - { 0b00000000, 0b01100000, 0b01100000, 0b00000000, 0b00000000, 0b00000000 }, // . { 0b00100000, 0b00010000, 0b00001000, 0b00000100, 0b00000010, 0b00000000 }, // / { 0b00111110, 0b01010001, 0b01001001, 0b01000101, 0b00111110, 0b00000000 }, // 0 { 0b00000000, 0b01000010, 0b01111111, 0b01000000, 0b00000000, 0b00000000 }, // 1 { 0b01000010, 0b01100001, 0b01010001, 0b01001001, 0b01000110, 0b00000000 }, // 2 { 0b00100001, 0b01000001, 0b01000101, 0b01001011, 0b00110001, 0b00000000 }, // 3 { 0b00011000, 0b00010100, 0b00010010, 0b01111111, 0b00010000, 0b00000000 }, // 4 // 20 { 0b00100111, 0b01000101, 0b01000101, 0b01000101, 0b00111001, 0b00000000 }, // 5 { 0b00111100, 0b01001010, 0b01001001, 0b01001001, 0b00110000, 0b00000000 }, // 6 { 0b00000011, 0b01110001, 0b00001001, 0b00000101, 0b00000011, 0b00000000 }, // 7 { 0b00110110, 0b01001001, 0b01001001, 0b01001001, 0b00110110, 0b00000000 }, // 8 { 0b00000110, 0b01001001, 0b01001001, 0b00101001, 0b00011110, 0b00000000 }, // 9 { 0b00000000, 0b01101100, 0b01101100, 0b00000000, 0b00000000, 0b00000000 }, // : { 0b00000000, 0b01010110, 0b00110110, 0b00000000, 0b00000000, 0b00000000 }, // ; { 0b00001000, 0b00010100, 0b00100010, 0b01000001, 0b00000000, 0b00000000 }, // < { 0b00010100, 0b00010100, 0b00010100, 0b00010100, 0b00010100, 0b00000000 }, // = { 0b00000000, 0b01000001, 0b00100010, 0b00010100, 0b00001000, 0b00000000 }, // > // 30 { 0b00000010, 0b00000001, 0b01010001, 0b00001001, 0b00000110, 0b00000000 }, // ? { 0b00110010, 0b01001001, 0b01111001, 0b01000001, 0b00111110, 0b00000000 }, // @ { 0b01111110, 0b00010001, 0b00010001, 0b00010001, 0b01111110, 0b00000000 }, // A { 0b01111111, 0b01001001, 0b01001001, 0b01001001, 0b00111110, 0b00000000 }, // B { 0b00111110, 0b01000001, 0b01000001, 0b01000001, 0b00100010, 0b00000000 }, // C { 0b01111111, 0b01000001, 0b01000001, 0b01000001, 0b00111110, 0b00000000 }, // D { 0b01111111, 0b01001001, 0b01001001, 0b01001001, 0b01001001, 0b00000000 }, // E { 0b01111111, 0b00001001, 0b00001001, 0b00001001, 0b00000001, 0b00000000 }, // F { 0b00111110, 0b01000001, 0b01001001, 0b01001001, 0b00111010, 0b00000000 }, // G { 0b01111111, 0b00001000, 0b00001000, 0b00001000, 0b01111111, 0b00000000 }, // H // 40 { 0b01000001, 0b01000001, 0b01111111, 0b01000001, 0b01000001, 0b00000000 }, // I { 0b00110000, 0b01000001, 0b01000001, 0b00111111, 0b00000001, 0b00000000 }, // J { 0b01111111, 0b00001000, 0b00010100, 0b00100010, 0b01000001, 0b00000000 }, // K { 0b01111111, 0b01000000, 0b01000000, 0b01000000, 0b01000000, 0b00000000 }, // L { 0b01111111, 0b00000010, 0b00001100, 0b00000010, 0b01111111, 0b00000000 }, // M { 0b01111111, 0b00000100, 0b00001000, 0b00010000, 0b01111111, 0b00000000 }, // N { 0b00111110, 0b01000001, 0b01000001, 0b01000001, 0b00111110, 0b00000000 }, // O { 0b01111111, 0b00001001, 0b00001001, 0b00001001, 0b00000110, 0b00000000 }, // P { 0b00111110, 0b01000001, 0b01010001, 0b00100001, 0b01011110, 0b00000000 }, // Q { 0b01111111, 0b00001001, 0b00001001, 0b00011001, 0b01100110, 0b00000000 }, // R // 50 { 0b01000110, 0b01001001, 0b01001001, 0b01001001, 0b00110001, 0b00000000 }, // S { 0b00000001, 0b00000001, 0b01111111, 0b00000001, 0b00000001, 0b00000000 }, // T { 0b00111111, 0b01000000, 0b01000000, 0b01000000, 0b00111111, 0b00000000 }, // U { 0b00001111, 0b00110000, 0b01000000, 0b00110000, 0b00001111, 0b00000000 }, // V { 0b00111111, 0b01000000, 0b00111000, 0b01000000, 0b00111111, 0b00000000 }, // W { 0b01100011, 0b00010100, 0b00001000, 0b00010100, 0b01100011, 0b00000000 }, // X { 0b00000011, 0b00000100, 0b01111000, 0b00000100, 0b00000011, 0b00000000 }, // Y { 0b01100001, 0b01010001, 0b01001001, 0b01000101, 0b01000011, 0b00000000 }, // Z { 0b01111111, 0b01000001, 0b01000001, 0b00000000, 0b00000000, 0b00000000 }, // [ { 0b00000010, 0b00000100, 0b00001000, 0b00010000, 0b00100000, 0b00000000 }, // '\' // 60 { 0b00000000, 0b00000000, 0b01000001, 0b01000001, 0b01111111, 0b00000000 }, // ] { 0b00000100, 0b00000010, 0b00000001, 0b00000010, 0b00000100, 0b00000000 }, // ^ { 0b01000000, 0b01000000, 0b01000000, 0b01000000, 0b01000000, 0b00000000 }, // _ { 0b00000000, 0b00000001, 0b00000010, 0b00000100, 0b00000000, 0b00000000 }, // ` { 0b00100000, 0b01010100, 0b01010100, 0b01010100, 0b01111000, 0b00000000 }, // a { 0b01111111, 0b01001000, 0b01000100, 0b01000100, 0b00111000, 0b00000000 }, // b { 0b00111000, 0b01000100, 0b01000100, 0b01000100, 0b00100000, 0b00000000 }, // c { 0b00111000, 0b01000100, 0b01000100, 0b01001000, 0b01111111, 0b00000000 }, // d { 0b00111000, 0b01010100, 0b01010100, 0b01010100, 0b00011000, 0b00000000 }, // e { 0b00001000, 0b01111110, 0b00001001, 0b00000001, 0b00000010, 0b00000000 }, // f // 70 { 0b00001100, 0b01010010, 0b01010010, 0b01010010, 0b00111110, 0b00000000 }, // g { 0b01111111, 0b00001000, 0b00000100, 0b00000100, 0b01111000, 0b00000000 }, // h { 0b00000000, 0b01000100, 0b01111101, 0b01000000, 0b00000000, 0b00000000 }, // i { 0b00100000, 0b01000000, 0b01000100, 0b00111101, 0b00000000, 0b00000000 }, // j { 0b01111111, 0b00010000, 0b00101000, 0b01000100, 0b00000000, 0b00000000 }, // k { 0b00000000, 0b01000001, 0b01111111, 0b01000000, 0b00000000, 0b00000000 }, // l { 0b01111000, 0b00000100, 0b00001000, 0b00000100, 0b01111000, 0b00000000 }, // m { 0b01111100, 0b00001000, 0b00000100, 0b00000100, 0b01111000, 0b00000000 }, // n { 0b00111000, 0b01000100, 0b01000100, 0b01000100, 0b00111000, 0b00000000 }, // o { 0b01111100, 0b00010100, 0b00010100, 0b00010100, 0b00001000, 0b00000000 }, // p // 80 { 0b00001000, 0b00010100, 0b00010100, 0b01111100, 0b00000000, 0b00000000 }, // q { 0b01111100, 0b00001000, 0b00000100, 0b00000100, 0b00001000, 0b00000000 }, // r { 0b01001000, 0b01010100, 0b01010100, 0b01010100, 0b00100000, 0b00000000 }, // s { 0b00000100, 0b00111111, 0b01000100, 0b01000000, 0b00100000, 0b00000000 }, // t { 0b00111100, 0b01000000, 0b01000000, 0b00100000, 0b01111100, 0b00000000 }, // u { 0b00011100, 0b00100000, 0b01000000, 0b00100000, 0b00011100, 0b00000000 }, // v { 0b00111100, 0b01000000, 0b00110000, 0b01000000, 0b00111100, 0b00000000 }, // w { 0b01000100, 0b00101000, 0b00010000, 0b00101000, 0b01000100, 0b00000000 }, // x { 0b00001100, 0b01010000, 0b01010000, 0b01010000, 0b00111100, 0b00000000 }, // y { 0b01000100, 0b01100100, 0b01010100, 0b01001100, 0b01000100, 0b00000000 }, // z // 90 { 0b00000000, 0b00001000, 0b00110110, 0b01000001, 0b00000000, 0b00000000 }, // { { 0b00000000, 0b00000000, 0b01111111, 0b00000000, 0b00000000, 0b00000000 }, // | { 0b00000000, 0b01000001, 0b00110110, 0b00001000, 0b00000000, 0b00000000 }, // } { 0b00001000, 0b00000100, 0b00000100, 0b00001000, 0b00000100, 0b00000000 }, // ~ => элемент 94, ASCII 126 => адрес 126-32, далее пропущены 16*4 символов { 0b01111100, 0b00010010, 0b00010001, 0b00010001, 0b01111111, 0b00000000 }, // А => элемент 95, win-1251 192 => адрес 192-32-16*4-1 { 0b01111111, 0b01001001, 0b01001001, 0b01001001, 0b00110001, 0b00000000 }, // Б { 0b01111111, 0b01001001, 0b01001001, 0b01001001, 0b00110110, 0b00000000 }, // В { 0b01111111, 0b00000001, 0b00000001, 0b00000001, 0b00000001, 0b00000000 }, // Г { 0b11000000, 0b01111100, 0b01000010, 0b01000001, 0b01111111, 0b11000000 }, // Д { 0b01111111, 0b01001001, 0b01001001, 0b01001001, 0b01000001, 0b00000000 }, // Е { 0b01110111, 0b00001000, 0b01111111, 0b00001000, 0b01110111, 0b00000000 }, // Ж { 0b00100010, 0b01000001, 0b01000001, 0b01001001, 0b00110110, 0b00000000 }, // З { 0b01111111, 0b00010000, 0b00001000, 0b00000100, 0b01111111, 0b00000000 }, // И { 0b01111111, 0b00010000, 0b00001001, 0b00000100, 0b01111111, 0b00000000 }, // Й { 0b01111111, 0b00001000, 0b00010100, 0b00100010, 0b01000001, 0b00000000 }, // К { 0b01111100, 0b00000010, 0b00000001, 0b00000001, 0b01111111, 0b00000000 }, // Л { 0b01111111, 0b00000010, 0b00000100, 0b00000010, 0b01111111, 0b00000000 }, // М { 0b01111111, 0b00001000, 0b00001000, 0b00001000, 0b01111111, 0b00000000 }, // Н { 0b00111110, 0b01000001, 0b01000001, 0b01000001, 0b00111110, 0b00000000 }, // О { 0b01111111, 0b00000001, 0b00000001, 0b00000001, 0b01111111, 0b00000000 }, // П { 0b01111111, 0b00010001, 0b00010001, 0b00010001, 0b00001110, 0b00000000 }, // Р // 111 { 0b00111110, 0b01000001, 0b01000001, 0b01000001, 0b00100010, 0b00000000 }, // С // 112 { 0b00000001, 0b00000001, 0b01111111, 0b00000001, 0b00000001, 0b00000000 }, // Т { 0b01000111, 0b01001000, 0b01010000, 0b01010000, 0b00111111, 0b00000000 }, // У { 0b00011100, 0b00100010, 0b01111111, 0b00100010, 0b00011100, 0b00000000 }, // Ф { 0b01100011, 0b00010100, 0b00001000, 0b00010100, 0b01100011, 0b00000000 }, // Х { 0b00111111, 0b01000000, 0b01000000, 0b01000000, 0b01111111, 0b11000000 }, // Ц { 0b00001111, 0b00010000, 0b00010000, 0b00010000, 0b01111111, 0b00000000 }, // Ч { 0b01111111, 0b01000000, 0b01111100, 0b01000000, 0b01111111, 0b00000000 }, // Ш { 0b01111111, 0b01000000, 0b01111100, 0b01000000, 0b01111111, 0b11000000 }, // Щ { 0b00000001, 0b01111111, 0b01001000, 0b01001000, 0b00110000, 0b00000000 }, // Ъ { 0b01111111, 0b01001000, 0b01001000, 0b00110000, 0b01111111, 0b00000000 }, // Ы { 0b01111111, 0b01001000, 0b01001000, 0b01001000, 0b00110000, 0b00000000 }, // Ь { 0b00100010, 0b01001001, 0b01001001, 0b01001001, 0b00111110, 0b00000000 }, // Э { 0b01111111, 0b00001000, 0b00111110, 0b01000001, 0b00111110, 0b00000000 }, // Ю { 0b01110110, 0b00001001, 0b00001001, 0b00001001, 0b01111111, 0b00000000 }, // Я { 0b00000000, 0b00100100, 0b01010100, 0b01010100, 0b01111000, 0b01000000 }, // а { 0b00000000, 0b00111000, 0b01010100, 0b01010100, 0b00100100, 0b00000000 }, // б { 0b00000000, 0b01111100, 0b01010100, 0b01010100, 0b00101000, 0b00000000 }, // в { 0b00000000, 0b01111100, 0b00000100, 0b00000100, 0b00000000, 0b00000000 }, // г { 0b00000000, 0b10011000, 0b10100100, 0b10100100, 0b01111100, 0b00000000 }, // д { 0b00000000, 0b00111000, 0b01010100, 0b01010100, 0b01011000, 0b00000000 }, // е { 0b01101100, 0b00010000, 0b01111100, 0b00010000, 0b01101100, 0b00000000 }, // ж { 0b00000000, 0b00101000, 0b01000100, 0b01010100, 0b00101000, 0b00000000 }, // з { 0b00000000, 0b00111100, 0b01000000, 0b01000000, 0b01111100, 0b00000000 }, // и { 0b00000000, 0b00111100, 0b01000000, 0b01000010, 0b01111100, 0b00000000 }, // й { 0b00000000, 0b01111100, 0b00010000, 0b00101000, 0b01000100, 0b00000000 }, // к { 0b00000000, 0b01110000, 0b00001000, 0b00000100, 0b01111100, 0b00000000 }, // л { 0b01111100, 0b00001000, 0b00010000, 0b00001000, 0b01111100, 0b00000000 }, // м { 0b00000000, 0b01111100, 0b00010000, 0b00010000, 0b01111100, 0b00000000 }, // н { 0b00000000, 0b00111000, 0b01000100, 0b01000100, 0b00111000, 0b00000000 }, // о { 0b00000000, 0b01111100, 0b00000100, 0b00000100, 0b01111100, 0b00000000 }, // п { 0b00000000, 0b11111100, 0b01000100, 0b01000100, 0b00111000, 0b00000000 }, // р { 0b00000000, 0b00111000, 0b01000100, 0b01000100, 0b00101000, 0b00000000 }, // с { 0b00000000, 0b00000100, 0b01111100, 0b00000100, 0b00000000, 0b00000000 }, // т { 0b00000000, 0b10011100, 0b10100000, 0b10100000, 0b01111100, 0b00000000 }, // у { 0b00111000, 0b01000100, 0b11111110, 0b01000100, 0b00111000, 0b00000000 }, // ф { 0b00000000, 0b01101100, 0b00010000, 0b00010000, 0b01101100, 0b00000000 }, // х { 0b00000000, 0b00111100, 0b01000000, 0b01000000, 0b01111100, 0b11000000 }, // ц { 0b00000000, 0b00001100, 0b00010000, 0b00010000, 0b01111100, 0b00000000 }, // ч { 0b01111100, 0b01000000, 0b01111000, 0b01000000, 0b01111100, 0b00000000 }, // ш { 0b01111100, 0b01000000, 0b01111000, 0b01000000, 0b01111100, 0b11000000 }, // щ { 0b00000100, 0b01111100, 0b01010000, 0b01010000, 0b00100000, 0b00000000 }, // ъ { 0b01111100, 0b01010000, 0b01010000, 0b00100000, 0b01111100, 0b00000000 }, // ы { 0b00000000, 0b01111100, 0b01010000, 0b01010000, 0b00100000, 0b00000000 }, // ь { 0b00000000, 0b01000100, 0b01010100, 0b01010100, 0b00111000, 0b00000000 }, // э { 0b01111100, 0b00010000, 0b00111000, 0b01000100, 0b00111000, 0b00000000 }, // ю { 0b00000000, 0b01001000, 0b00110100, 0b00010100, 0b01111100, 0b00000000 } // я }; int messageIndeces[MAX_LEN] = { 110, 143, 135, 129, 132, 145, 12, 0, 51, 79, 76, 84, 65, 85, 14, 82, 85, 1, 0, 26, 9 }; // индексы отображаемого сообщения в массиве шрифта int messageLen = 21; // длина текущего сообщения byte buffer[NUM_LINES][NUM_DEVICES] = { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xf }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xf }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 } }; void setup() { InitLedPanel(); InitBuffer(); } // Задаёт назначение выводов, инициализирует SPI и последовательный порт. void InitLedPanel() { pinMode(A, OUTPUT); pinMode(B, OUTPUT); pinMode(C, OUTPUT); pinMode(ENA, OUTPUT); pinMode(STB, OUTPUT); pinMode(CLK, OUTPUT); pinMode(DATA, OUTPUT); digitalWrite(A, LOW); digitalWrite(B, LOW); digitalWrite(C, LOW); digitalWrite(ENA, HIGH); digitalWrite(STB, HIGH); SPI.begin(); Serial.begin(115200); } void loop() { for (int n = 0; n < messageLen; n++) { // для каждого символа сообщения for (int col = 0; col < SYM_WIDTH; col++) { // для каждого столбца символа ShiftBuffer(); // сдвигаем буфер, освобождается крайний правый столбец byte symCol = characters[messageIndeces[n]][col]; // текущий столбец символа PushColumn(symCol); // размещаем столбец символа в крайнем правом столбце буфера // Несколько раз отображаем текущий буфер (регулировка скорости бегущей строки): for (int r = 0; r < RPT; r++) { DisplayBuffer(); } } } } // Вдвигаем столбец символа в крайний правый столбец буфера. void PushColumn(byte col) { for (int i = 0; i < NUM_LINES; i++) { // каждый бит столбца размещается в своей строке byte curBit = (col >> i) & 1; // текущий бит столбца buffer[i][NUM_DEVICES - 1] = buffer[i][NUM_DEVICES - 1] | curBit; // размещаем в младшем разряде последнего байта буфера } } // Инициализирует пустой буфер. void InitBuffer(void) { for (int l = 0; l < NUM_LINES; l++) { for (int d = 0; d < NUM_DEVICES; d++) { buffer[l][d] = 0x00; } } } // Сдвигает содержимое буфера на 1 столбец влево. void ShiftBuffer() { for (int l = 0; l < NUM_LINES; l++) { byte msb = 0; // старший бит числа for (int d = 0; d < NUM_DEVICES; d++) { if (d < NUM_DEVICES - 1) { msb = buffer[l][d + 1] >> 7; // запомнили старший бит следующего числа } else { msb = 0; } buffer[l][d] = (buffer[l][d] << 1) | msb; // сдвигаем на 1 бит и младший заполняем значением предыдущего старшего } } // Serial.println("Shifted:"); // Для отладочного вывода, если надо // for (int l = 0; l < NUM_LINES; l++) { // for (int d = 0; d < NUM_DEVICES; d++) { // Serial.print(buffer[l][d], HEX); // Serial.print(" "); // } // Serial.println(); // } } // Выводит текущий буфер на LED панель. void DisplayBuffer() { for (int i = 0; i < NUM_LINES; i++) { WriteLine(i, buffer[i]); } } // Выводит на LED панель заданную строку. void WriteLine(int line, byte *ar) { SetLine(line); digitalWrite(STB, LOW); for (int i = 0; i < NUM_DEVICES; i++) { SPI.transfer(ar[i] ^ 0xff); // ^0xFF = инверсия изображения } digitalWrite(STB, HIGH); } // Выбирает строку LED панели по номеру. void SetLine(int lineNumber) { digitalWrite(ENA, LOW); delay(1); // Выставляет номер строки LED панели: digitalWrite(A, lineNumber & 1); // выставляем младший бит digitalWrite(B, (lineNumber >> 1) & 1); // выставляем средний бит digitalWrite(C, (lineNumber >> 2) & 1); // выставляем старший бит digitalWrite(ENA, HIGH); } // Определяет индекс символа в таблице шрифта. int IndexInFontTable(String sym) { String w1251Sym = Utf8_Win1251(sym); // для латиницы ничего не меняется, для кириллицы получаем значение больше 127 и меньшее 256. byte asciiVal = (byte)w1251Sym[0]; byte fontIndex = asciiVal - 32; // индекс символа в таблице шрифта if (asciiVal > 126) { fontIndex -= 65; // 65 = 16*4+1 - столько символов пропущено в таблице шрифта, и сдвиг индекса на 1 } Serial.println("orig=[" + sym + "]; win1251=0x" + w1251Sym + "; table_index=" + (String)fontIndex); return fontIndex; } // Перекодирует строку из UTF-8 в Windows-1251. String Utf8_Win1251(String source) { String target; char m[2] = { '0', '\0' }; int k = source.length(); int i = 0; while (i < k) { unsigned char n = source[i]; i++; if (n >= 0xC0) { switch (n) { case 0xD0: { n = source[i]; i++; if (n == 0x81) { n = 0xA8; break; } if (n >= 0x90 && n <= 0xBF) n = n + 0x30; break; } case 0xD1: { n = source[i]; i++; if (n == 0x91) { n = 0xB8; break; } if (n >= 0x80 && n <= 0x8F) n = n + 0x70; break; } } } m[0] = n; target = target + String(m); } return target; } // Событие по приходу данных в последовательный порт. void serialEvent() { while (Serial.available()) { char inSym = (char)Serial.read(); // принимаем символ из порта if ((inSym != '\n') && (inSym != '\r')) { // если пришёл НЕ символ переноса строки int indexInFontTable = IndexInFontTable((String)inSym); // ищем его индекс в таблице шрифтов inputIndeces[curInputIndex] = indexInFontTable; // размещаем индекс во временном буфере curInputIndex += 1; // запоминаем текущее положение в буфере } else { if (curInputIndex != 0) { // защита от двойного сброса InitBuffer(); // Serial.println("New message indeces:"); for (int i = 0; i < curInputIndex; i++) { messageIndeces[i] = inputIndeces[i]; // обновляем индексы выводимого сообщения // Serial.print(String(messageIndeces[i]) + " "); } // Serial.println(); messageLen = curInputIndex; // запоминаем размер сообщения curInputIndex = 0; } } } }
Описание скетча – в видео, а также в комментариях.
Download attachments:
- Скачать datasheet на SM74HC595 (1057 Downloads)
- Скачать datasheet на SN74HC595 (683 Downloads)
- Программа генерации массивов шрифта (19 Downloads)
Поблагодарить автора:
Поделиться
Related items
4 comments
-
SMaltsev Среда, 29 Январь 2020 06:07 Ссылка на комментарий
Добрый день.
А что собственно за панель? Где купить? -
aave1 Четверг, 30 Январь 2020 17:59 Ссылка на комментарий
Решение подойдёт для любой LED-панели с подобным типом подключения. Конкретно это довольно старая модель, на печатной плате маркировка DISP95.PCB by ZHOU 2004/02/28, вряд ли её можно купить сегодня. Но такого типа панели используются там, где применяются дисплеи типа "бегущая строка".
-
Rf Воскреснье, 15 Декабрь 2024 13:24 Ссылка на комментарий
Когда продолжение с динамическим текстом? И ещё чтобы можно было вводить текст из монитора порта