Рейтинг@Mail.ru

Как подключить LED-панель 95x8 к Arduino

автор:
4 comments Arduino
Print Friendly, PDF & Email
Подключаем к Arduino LED панель, составленную из нескольких модулей 5x8 светодиодов.

Для проекта нам понадобятся:

  • светодиодная модульная панель;
  • 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 мА; умножаем на число модулей и получаем весьма приличный ток и, соответственно, потребляемую мощность.

Разъём управления LED-панелью
Разъём управления LED-панелью

Для того чтобы управлять таким большим числом светодиодов (19*5*8=760), разумеется, нужно иметь сдвиговый регистр. Что мы и наблюдаем: данная LED панель использует 12 сдвиговых регистров SM74HC595D, расположенных на её обратной стороне.

Сдвиговый регистр SM74HC595D светодиодной панели
Сдвиговый регистр 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)
GNDGND

Я не говорю, что это единственно возможный способ подключения светодиодной панели. Но если изучить техническое описание микросхемы HC595, то станет понятно, что для работы с выводами STB, CLK и DATA удобно использовать аппаратные ножки Arduino режима SPI (SS, SCK, MOSI). А для работы с выводами адресации (ADDR0..ADDR2) и разрешения записи (ENA) световой панели, в принципе, можно использовать любые цифровые пины Arduino.

Подключение Arduino Uno к разъёму управления светодиодной панели
Подключение Arduino Uno к разъёму управления светодиодной панели

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);
}

После загрузки и выполнения скетча получится что-то типа этого:

Управление светодиодной панелью с помощью Arduino
Управление светодиодной панелью с помощью Arduino

В динамике работа скетча со светодиодной панелью выглядит так:

Ключевые моменты в скетче следующие:

  • запись данных происходит построчно;
  • чтобы записать одну строку из 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);
}

В результате выполнения этого скетча получится вот такое изображение:

Вывод статического текста на светодиодную панель с помощью Arduino
Вывод статического текста на светодиодную панель с помощью Arduino
Вывод статического текста на светодиодную панель с помощью Arduino
Вывод статического текста на светодиодную панель с помощью Arduino
Вывод статического текста на светодиодную панель с помощью Arduino
Вывод статического текста на светодиодную панель с помощью Arduino

Конечно, всё это довольно занятно, но чаще всего такого рода панели используют для вывода текста в виде бегущей строки. Кроме того, довольно утомительно для каждого нового рисунка или надписи прописывать все 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;
      }
    }
  }
}

Описание скетча – в видео, а также в комментариях.

Last modified onЧетверг, 16 Январь 2025 14:42 Read 9651 times
Ключевые слова: :

Поблагодарить автора:

Поделиться

Print Friendly, PDF & Email

4 comments

  • SMaltsev
    SMaltsev Среда, 29 Январь 2020 06:07 Ссылка на комментарий

    Добрый день.
    А что собственно за панель? Где купить?

  • aave1
    aave1 Четверг, 30 Январь 2020 17:59 Ссылка на комментарий

    Решение подойдёт для любой LED-панели с подобным типом подключения. Конкретно это довольно старая модель, на печатной плате маркировка DISP95.PCB by ZHOU 2004/02/28, вряд ли её можно купить сегодня. Но такого типа панели используются там, где применяются дисплеи типа "бегущая строка".

  • Алексей
    Алексей Среда, 26 Февраль 2020 07:29 Ссылка на комментарий

    Отличный проект, ждем продолжение!

  • Rf
    Rf Воскреснье, 15 Декабрь 2024 13:24 Ссылка на комментарий

    Когда продолжение с динамическим текстом? И ещё чтобы можно было вводить текст из монитора порта

  1. Arduino это...
  2. Arduino это...
  3. Arduino это...
Отличный способ начать знакомство с электроникой, микроконтроллерами и программированием!
Замечательное средство для создания собственных электронных устройств, которые пригодятся в быту или для развлечения!
Уникальный конструктор, для которого разработаны десятки совместимых датчиков и модулей!
next
prev