Как подключить 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;
}
}
}
}
Описание скетча – в видео, а также в комментариях.
Скачать вложения:
- Скачать datasheet на SM74HC595 (1230 Скачиваний)
- Скачать datasheet на SN74HC595 (827 Скачиваний)
- Программа генерации массивов шрифта (302 Скачиваний)
Поблагодарить автора:
Поделиться
Похожие материалы (по тегу)
4 комментарии
-
-
aave1 30.01.2020 17:59 КомментироватьРешение подойдёт для любой LED-панели с подобным типом подключения. Конкретно это довольно старая модель, на печатной плате маркировка DISP95.PCB by ZHOU 2004/02/28, вряд ли её можно купить сегодня. Но такого типа панели используются там, где применяются дисплеи типа "бегущая строка".
-
-
Rf 15.12.2024 13:24 КомментироватьКогда продолжение с динамическим текстом? И ещё чтобы можно было вводить текст из монитора порта





