Компоненты · 15 мин чтения

Светодиодная матрица MAX7219 и Arduino: бегущая строка с MD_Parola

MAX7219 и Arduino: подключение матриц 8x8 и модулей 4-в-1 8x32, бегущая строка с MD_Parola, исправление зеркального текста через FC16_HW, часы и код для ESP32.

MAX7219 — недорогая микросхема-драйвер, которая сама управляет 64 светодиодами, и это самый простой способ подключить светодиодную матрицу к Arduino. Обычно она встречается в двух вариантах: модуль с одной точечной матрицей 8x8 и модуль 4-в-1 с точечной матрицей 8x32, где четыре матрицы стоят рядом. MAX7219 нужны всего 3 сигнальных провода от Arduino (DIN, CS, CLK): микросхема сама сканирует строки сотни раз в секунду, задаёт яркость и помнит состояние каждого светодиода. Arduino остаётся свободной для бегущей строки, часов, табло или анимаций, а дисплей можно сделать сколь угодно длинным, соединяя модули друг за другом.

В этом руководстве разберём, как работает MAX7219, как подключить модуль к Arduino Uno, ESP32 и Raspberry Pi Pico по SPI, как соединять модули в цепочку и как вывести первую бегущую строку с библиотекой MD_Parola. Затем исправим проблему перевёрнутого или зеркального текста, на которой спотыкается большинство новичков, выбрав правильный тип оборудования (FC16_HW), рассчитаем яркость и потребление, выведем символы с диакритикой и собственные символы, а также соберём часы с RTC-модулем и попиксельную анимацию. В последних разделах — отправка сообщений с телефона через ESP32, код на MicroPython для Pico и решения самых частых ошибок.

Светодиодный модуль MAX7219 4-в-1 с точечной матрицей 8x32
Точечный модуль MAX7219 4-в-1: четыре матрицы 8x8, под каждой своя микросхема MAX7219; питание и сигналы подаются со стороны IN, а следующий модуль подключается со стороны OUT.

Что такое MAX7219 и как он работает?

64 светодиода невозможно подключить к отдельным пинам Arduino, поэтому в светодиодных матрицах используется мультиплексирование. В матрице 8x8 светодиоды расположены в сетке из 8 строк и 8 столбцов. В каждый момент MAX7219 включает только одну группу светодиодов (в даташите это «digit», разряд; на матрице это строка или столбец), через остальные линии выбирает, какие из восьми светодиодов этой группы загорятся, и переходит к следующей группе. Все восемь групп сканируются примерно 800 раз в секунду; глаз не успевает за такой скоростью, поэтому изображение кажется неподвижным. При этом микросхема хранит рисунок каждой группы в собственной ОЗУ 8x8 бит, ограничивает ток светодиодов одним резистором и регулирует яркость в 16 ступеней.

Arduino общается с MAX7219 по 3-проводному последовательному интерфейсу, который очень похож на SPI. Каждая команда — 16-битный пакет: старший байт выбирает регистр для записи, младший несёт значение. Пока CS (в даташите LOAD) в состоянии LOW, биты вдвигаются с DIN по каждому нарастающему фронту CLK; когда CS снова становится HIGH, пакет записывается в регистр. Идея та же, что у сдвигового регистра 74HC595, но MAX7219 к тому же сам занимается сканированием и ограничением тока, так что работы гораздо меньше. Как устроен SPI в целом, рассказано в статье Что такое SPI?.

ХарактеристикаЗначение
Напряжение питания4,0–5,5 V (на практике 5 V)
Интерфейс3-проводный последовательный (DIN, CLK, CS), до 10 MHz
Управляемые светодиоды64 (матрица 8x8 или восемь разрядов 7-сегментного индикатора)
Частота сканированияОбычно 800 Hz (8 разрядов)
Ток сегментаЗадаётся резистором RSET, максимум 40 mA (на модулях обычно 10 kΩ)
Яркость16 ступеней, коэффициент заполнения от 1/32 до 31/32
Ток в режиме отключенияОколо 150 µA
Порог входного уровня HIGHНе менее 3,5 V (при питании 5 V)
Соединение в цепочкуВыход DOUT на вход DIN следующей микросхемы

У MAX7219 14 адресуемых регистров, но для светодиодной матрицы нужны те, что в таблице ниже. Библиотеки настраивают их за тебя; и всё же понимание их назначения очень помогает при отладке. Например, при первой подаче питания микросхема стартует в режиме shutdown (отключение) с тёмным дисплеем, поэтому если код инициализации не выполнился, модуль выглядит совершенно мёртвым. Родственная микросхема MAX7221 имеет те же регистры, но у неё настоящий SPI-совместимый пин CS и драйверы сегментов с ограничением скорости нарастания фронтов, которые снижают электромагнитные помехи.

АдресРегистрНазначение
0x00No-Op (нет операции)Оставляет эту микросхему без изменений, пока пакет идёт к другой микросхеме цепочки
0x01–0x08Digit 0–7 (разряды)8-битный рисунок светодиодов каждой из восьми строк (или столбцов)
0x09Decode Mode (режим декодирования)BCD-декодирование для 7-сегментных разрядов; для матрицы выключено (0x00)
0x0AIntensity (яркость)Яркость, от 0x0 до 0xF
0x0BScan Limit (число разрядов)Сколько разрядов сканировать; для матрицы 7 (все восемь)
0x0CShutdown (отключение)0: режим отключения, 1: нормальная работа
0x0FDisplay Test (тест дисплея)1: все светодиоды горят на полной яркости

Одиночный модуль 8x8 или модуль 4-в-1 8x32?

Два самых распространённых модуля собраны по одной схеме; разница в том, как светодиодная матрица подключена к MAX7219, и в форме платы. Одиночный модуль на зелёной плате несёт MAX7219 в корпусе DIP (24 вывода) и матрицу 8x8, например 1088AS; с одного края у него входные пины (VCC, GND, DIN, CS, CLK), с другого — выходные (VCC, GND, DOUT, CS, CLK). На синем модуле 4-в-1 четыре микросхемы MAX7219 в корпусе SMD стоят под матрицами, а четыре блока уже соединены в цепочку на заводе; плата обычно маркирована FC-16. Для длинной бегущей строки модуль 4-в-1 и дешевле, и аккуратнее.

Одиночный светодиодный матричный модуль MAX7219 8x8
Одиночный модуль MAX7219 8x8: зелёная плата, MAX7219 в корпусе DIP и точечная матрица 8x8; обычно правильно отображает с типом оборудования GENERIC_HW в коде.
ХарактеристикаОдиночный модуль 8x8Модуль 4-в-1 8x32 (FC-16)
Разрешение8x8 = 64 светодиода32x8 = 256 светодиодов
Количество MAX72191 (корпус DIP)4 (корпус SMD)
Тип оборудования в кодеОбычно GENERIC_HWОбычно FC16_HW
Символов на экране одновременно14–6 (зависит от ширины букв)
Максимальный ток на полной яркостиОколо 330 mAОколо 1,3 A
Лучше всего подходит дляИконки, кубики, смайлики, небольшие игрыБегущая строка, часы, табло

Подключение MAX7219: Arduino Uno, ESP32 и Pico

У модуля пять пинов: VCC и GND для питания, DIN для данных, CLK для тактового сигнала и CS для выбора микросхемы (загрузки). По умолчанию библиотеки MD_Parola и MD_MAX72XX используют аппаратный SPI, поэтому DIN нужно подключить к пину MOSI твоей платы, а CLK — к пину SCK; CS может быть любым цифровым пином. Где на твоей плате находятся пины аппаратного SPI, можно посмотреть в утилите распиновки. Провода всегда подключай к входной стороне (IN) модуля, к краю с надписью DIN; край с надписью DOUT предназначен для следующего модуля.

Пин модуляArduino UnoESP32Raspberry Pi Pico
VCC5V5V (VIN)VBUS (5 V)
GNDGNDGNDGND
DIND11 (MOSI)GPIO 23 (MOSI)GP19 (SPI0 TX)
CSD10GPIO 5GP17
CLKD13 (SCK)GPIO 18 (SCK)GP18 (SPI0 SCK)
Не питай модуль MAX7219 от пина 3,3 V: микросхеме нужно не меньше 4 V. Даже при 5 V одиночный модуль 8x8 на полной яркости может потреблять больше 300 mA, а модуль 4-в-1 — до 1,3 A. USB-порт компьютера и USB-предохранитель Arduino ограничены 500 mA. Если Arduino перезагружается или изображение мерцает при увеличении яркости, питай модуль от внешнего блока питания 5 V (для модуля 4-в-1 не меньше 2 A) и обязательно соедини GND блока питания с GND Arduino.

У ESP32 и Pico логика на 3,3 V, а даташит MAX7219 при питании 5 V требует для уровня HIGH не менее 3,5 V. Получается, что сигналы 3,3 V формально чуть ниже границы. Большинство модулей всё равно работают, но с длинными проводами или с некоторыми партиями микросхем могут появляться случайные точки и искажённое изображение. Надёжное решение — буфер вроде 74HCT125, который принимает 3,3 V как HIGH и выдаёт 5 V, или быстрый преобразователь логических уровней на линиях DIN, CLK и CS. Часто проблему решает и укорачивание сигнальных проводов до 20 см и меньше.

Соединение модулей в цепочку (daisy chain)

Пин DOUT микросхемы MAX7219 выдаёт данные, поступившие на DIN, через 16,5 такта. Если соединить сторону OUT одного модуля со стороной IN следующего, DOUT идёт на DIN следующей микросхемы, а CLK и CS общие для всех микросхем. Так от Arduino по-прежнему отходят всего 3 сигнальных провода, а дисплей может состоять из 8, 16 или 32 блоков. При каждом обновлении библиотека прогоняет пакеты по всей цепочке, и когда CS поднимается, все микросхемы обновляются одновременно. На модулях 4-в-1 эти соединения уже есть на плате; чтобы собрать дисплей из 8 блоков (64x8) из двух модулей 4-в-1, достаточно соединить пины OUT первого модуля с пинами IN второго и задать в коде MAX_DEVICES равным 8.

В библиотеке номера блоков начинаются с модуля, ближайшего ко входу DIN, и растут влево; у MD_MAX72XX начало координат — верхний правый угол дисплея. Поэтому вход DIN должен оставаться справа, иначе текст не будет читаться правильно. С ростом цепочки настоящим ограничением становится питание: каждые 4–8 блоков заново подводи 5 V и GND толстыми проводами и ставь на входе питания каждого модуля электролитический конденсатор 10 µF и керамический 100 nF. Эту пару рекомендует и даташит — она сглаживает пульсации от импульсов тока светодиодов.

Установка библиотек: MD_MAX72XX и MD_Parola

Две самые популярные библиотеки для MAX7219 написаны одним автором (majicDesigns) и работают вместе. MD_MAX72XX делает низкоуровневую работу: инициализирует модули, пишет пиксели и столбцы, управляет шрифтом. MD_Parola строится поверх неё и добавляет бегущую строку, эффекты, выравнивание и зоны. Библиотека LedControl из старых примеров тоже работает, но прокрутку текста там приходится писать вручную. Если библиотеки ещё не устанавливались, сначала прочитай Как добавить библиотеку в Arduino.

  1. В Arduino IDE открой «Менеджер библиотек» (Library Manager): «Инструменты › Управлять библиотеками» (Tools › Manage Libraries), и найди MD_Parola.
  2. Установи библиотеку MD_Parola от majicDesigns. Когда появится вопрос о зависимостях, выбери Установить все (Install all), чтобы установилась и MD_MAX72XX.
  3. Подключи модуль по таблице выше, а для модуля 4-в-1 задай в коде HARDWARE_TYPE как FC16_HW и MAX_DEVICES как 4.
  4. Загрузи скетч. Если текст выглядит перевёрнутым, зеркальным или повёрнутым, не страшно: это исправляется одной строкой в разделе о типе оборудования ниже.

Первая программа: бегущая строка с MD_Parola

C++ (Arduino IDE)
1// MAX7219 4'lü modülde kayan yazı (MD_Parola) / Scrolling text on a 4-in-1 MAX7219 module (MD_Parola)
2#include <MD_Parola.h>
3#include <MD_MAX72xx.h>
4#include <SPI.h>
5
6#define HARDWARE_TYPE MD_MAX72XX::FC16_HW   // mavi PCB'li 4'lü modül / blue-PCB 4-in-1 module
7#define MAX_DEVICES 4                       // zincirdeki 8x8 blok sayısı / number of 8x8 blocks in the chain
8#define CS_PIN 10                           // DIN = D11 (MOSI), CLK = D13 (SCK)
9
10MD_Parola display = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
11
12void setup() {
13  display.begin();
14  display.setIntensity(4);                  // parlaklık 0–15 / brightness 0–15
15  display.displayClear();
16  // metin, hizalama, hız (ms/kare), bekleme (ms), giriş efekti, çıkış efekti
17  // text, alignment, speed (ms/frame), pause (ms), entry effect, exit effect
18  display.displayText("Merhaba BlueGrays!", PA_CENTER, 50, 1000, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
19}
20
21void loop() {
22  if (display.displayAnimate()) {           // efekt bittiğinde true döner / returns true when the effect ends
23    display.displayReset();                 // aynı yazıyı baştan başlat / restart the same text
24  }
25}

Самая важная строка — displayText(): она по порядку принимает текст, выравнивание (PA_LEFT, PA_CENTER, PA_RIGHT), время между кадрами в миллисекундах, паузу после того, как текст остановится, и эффекты входа и выхода. Чем меньше значение скорости, тем быстрее движется текст; 25–60 мс дают читаемую прокрутку. displayAnimate() за каждый вызов продвигает анимацию максимум на один кадр и сразу возвращает управление. Поэтому её нужно постоянно вызывать в loop(), и поэтому в остальном коде не стоит использовать delay(), иначе текст будет дёргаться. Как ждать, не блокируя программу, рассказано в статье millis() или delay().

ФункцияЧто делает
begin()Инициализирует модули, выводит их из режима отключения и очищает дисплей
displayText(текст, выравнивание, скорость, пауза, эффект_входа, эффект_выхода)Задаёт текст и эффекты для показа и запускает анимацию
displayAnimate()Рисует следующий кадр; возвращает true, когда эффект закончен
displayReset()Запускает тот же текст и эффект заново с начала
print("...")Показывает неподвижный текст без эффекта
setIntensity(0–15)Задаёт яркость всех модулей
setInvert(true)Меняет местами горящие и погасшие светодиоды (негатив)
displayShutdown(true)Переводит дисплей в режим отключения; false снова его включает
begin(2) и setZone(0, 0, 1)Делит дисплей на независимые зоны, у каждой свой текст и эффект

Кроме PA_SCROLL_LEFT есть много эффектов входа и выхода: PA_PRINT (текст появляется сразу), PA_SCROLL_UP и PA_SCROLL_DOWN (вертикальная прокрутка), PA_WIPE, PA_OPENING и PA_CLOSING (раскрытие из центра и сворачивание к центру), PA_FADE (плавное изменение яркости), PA_DISSOLVE, PA_BLINDS, PA_RANDOM и PA_GROW_UP. Чтобы увидеть их все, загрузи скетч Parola_Animation_Catalog из «Файл › Примеры › MD_Parola» (File › Examples); не забудь поменять строки HARDWARE_TYPE и MAX_DEVICES под свой модуль.

Выбор типа оборудования (FC16_HW) и исправление зеркального текста

Производители подключают матрицу 8x8 к MAX7219 по-разному: на одних платах линии «digit» микросхемы идут к строкам, на других — к столбцам, а порядок строк или столбцов может быть обратным. Библиотека компенсирует это настройкой HARDWARE_TYPE. Если тип выбран неправильно, модуль исправен, но буквы в каждом блоке 8x8 выглядят зеркальными, перевёрнутыми или повёрнутыми на 90 градусов. Поскольку в большинстве старых примеров в интернете стоит PAROLA_HW или GENERIC_HW, это самая частая проблема с синими модулями 4-в-1, которые продаются сегодня. В таблице ниже показано, что менять при каждом симптоме; она составлена для модуля 4-в-1 FC-16.

Что видноВероятная причинаРешение
Буквы в каждом блоке зеркально отражены слева направоВыбран PAROLA_HW, порядок столбцов обратныйИспользуй FC16_HW
Буквы в каждом блоке повёрнуты на 90 градусовВыбран GENERIC_HW, строки и столбцы поменяны местамиИспользуй FC16_HW
Буквы в каждом блоке перевёрнуты вверх ногамиВыбран ICSTATION_HW, порядок строк и столбцов обратныйИспользуй FC16_HW
Весь текст перевёрнут и бежит слева направоМодуль стоит наоборот, вход DIN слеваПоверни модуль на 180 градусов, чтобы вход DIN оказался справа
Правильно показывают только некоторые блоки, остальные пустые или с мусоромMAX_DEVICES не совпадает с реальным числом блоковЗадай MAX_DEVICES равным числу блоков
На одиночном зелёном модуле 8x8 сначала попробуй GENERIC_HW, на синем модуле 4-в-1 — FC16_HW. Если изображение всё ещё неправильное, перебери все четыре типа по очереди: каждый раз меняется только одна строка. Чтобы точно определить тип своего модуля, загрузи скетч MD_MAX72xx_HW_Mapper из примеров MD_MAX72XX: он зажигает светодиоды по одному и показывает в мониторе порта, какая строка и какой столбец куда подключены.

Яркость и потребление

Яркость зависит от двух вещей. Первая — резистор RSET на модуле: микросхема подаёт на светодиоды ток примерно в 100 раз больше тока, вытекающего из пина ISET. На модулях обычно стоит 10 kΩ, что даёт пиковый ток чуть больше 40 mA для красных светодиодов и около 40 mA для синих и белых. Вторая — регистр Intensity, который задаётся программно: значение n от 0 до 15 держит светодиоды включёнными (2n+1)/32 каждого интервала сканирования, так что 0 — минимальная (1/32), а 15 — максимальная (31/32) яркость. А поскольку каждая строка горит только одну восьмую времени, даже на полной яркости один светодиод в среднем потребляет около 5 mA. Прямое напряжение для светодиодов разных цветов можно посмотреть в утилите напряжений светодиодов.

Средний ток модуля можно грубо оценить так: I ≈ 8 mA + 5 mA × (2n+1)/32 × L. Здесь 8 mA — собственное потребление микросхемы, n — значение яркости, L — число горящих светодиодов на этом модуле (0–64); 5 mA — одна восьмая от пикового тока 40 mA. Например, при яркости 15 и всех 64 горящих светодиодах получается 8 + 5 × 0,97 × 64 ≈ 318 mA, что совпадает с 330 mA из даташита. Бегущая строка обычно зажигает лишь 25–35% светодиодов, поэтому реальное потребление гораздо ниже. В таблице ниже — типичные значения для модуля 4-в-1.

УсловиеНа один модульМодуль 4-в-1 (32x8)
Все светодиоды горят, яркость 15≈ 320 mA≈ 1,3 A
Все светодиоды горят, яркость 7≈ 160 mA≈ 630 mA
Бегущая строка (~30% светодиодов), яркость 7≈ 55 mA≈ 210 mA
Бегущая строка (~30% светодиодов), яркость 2≈ 25 mA≈ 90 mA
Режим отключения (displayShutdown)≈ 0,15 mA≈ 0,6 mA

Чтобы ночью дисплей не слепил, яркость можно регулировать автоматически с датчиком освещённости LDR, а когда рядом никого нет, усыплять дисплей вызовом displayShutdown(true). Для вывески с питанием от батареи введи значения из таблицы в калькулятор времени работы от батареи и узнай, на сколько часов её хватит. В помещении вместо 15 обычно достаточно яркости около 2–4: так мягче для глаз, а ток светодиодов падает примерно в три–шесть раз.

Символы с диакритикой и специальные символы (UTF-8)

Arduino IDE сохраняет код в UTF-8, поэтому буква вроде «é» или «ü» в тексте занимает не один байт, а два. MD_Parola ищет каждый байт в шрифте как номер символа, и буквы с диакритикой превращаются в бессмысленные значки. Стандартный шрифт библиотеки содержит весь набор Latin-1 (é, è, à, ñ, ç, ä, ö, ü, ß и другие), так что достаточно небольшой функции, которая переводит UTF-8 в коды шрифта. Буквы вне Latin-1, например турецкие ğ, ı и ş в примере ниже, приходится рисовать попиксельно и добавлять через addChar(). Скетч выполняет оба шага.

C++ (Arduino IDE)
1// MAX7219 + MD_Parola: Türkçe karakterli kayan yazı / Scrolling text with Turkish letters
2#include <MD_Parola.h>
3#include <MD_MAX72xx.h>
4#include <SPI.h>
5
6#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
7#define MAX_DEVICES 4
8#define CS_PIN 10
9
10MD_Parola display = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
11
12// Fontta olmayan 6 harf: ilk bayt genişlik, sonrakiler sütunlar (bit 0 = en üst satır)
13// 6 letters missing from the font: first byte is the width, then the columns (bit 0 = top row)
14const uint8_t trG[] = {5, 56, 69, 70, 85, 116};  // Ğ
15const uint8_t trg[] = {4, 25, 166, 166, 125};    // ğ
16const uint8_t trI[] = {3, 68, 125, 68};          // İ
17const uint8_t tri[] = {1, 124};                  // ı
18const uint8_t trS[] = {5, 70, 73, 201, 73, 49};  // Ş
19const uint8_t trs[] = {4, 88, 212, 84, 52};      // ş
20
21// UTF-8 metni fontun kodlarına çevirir / converts UTF-8 text to the font's codes
22void utf8ToFont(const char *src, char *dst, size_t size) {
23  size_t n = 0;
24  while (*src && n < size - 1) {
25    uint8_t c = *src++;
26    if (c < 0x80) { dst[n++] = c; continue; }            // ASCII aynen kalır / ASCII stays as is
27    if ((c & 0xE0) == 0xC0 && *src) {                    // 2 baytlık harf / 2-byte letter
28      uint16_t u = ((c & 0x1F) << 6) | (*src++ & 0x3F);
29      switch (u) {
30        case 0x11E: dst[n++] = (char)0x80; break;        // Ğ
31        case 0x11F: dst[n++] = (char)0x81; break;        // ğ
32        case 0x130: dst[n++] = (char)0x82; break;        // İ
33        case 0x131: dst[n++] = (char)0x83; break;        // ı
34        case 0x15E: dst[n++] = (char)0x84; break;        // Ş
35        case 0x15F: dst[n++] = (char)0x85; break;        // ş
36        default: dst[n++] = (u < 0x100) ? (char)u : '?'; // ç ö ü Ç Ö Ü ° fontta var / already in the font
37      }
38      continue;
39    }
40    while ((*src & 0xC0) == 0x80) src++;                 // desteklenmeyen karakteri atla / skip an unsupported character
41    dst[n++] = '?';
42  }
43  dst[n] = '\0';
44}
45
46char message[96];
47
48void setup() {
49  display.begin();
50  display.setIntensity(3);
51  display.addChar(0x80, trG);                // addChar begin()'den sonra / addChar after begin()
52  display.addChar(0x81, trg);
53  display.addChar(0x82, trI);
54  display.addChar(0x83, tri);
55  display.addChar(0x84, trS);
56  display.addChar(0x85, trs);
57  utf8ToFont("Günaydın İstanbul! Şehirde hava 22°C, çok güzel bir gün.", message, sizeof(message));
58  display.displayText(message, PA_LEFT, 40, 1000, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
59}
60
61void loop() {
62  if (display.displayAnimate()) display.displayReset();
63}

В массиве каждого своего символа первый байт — ширина буквы в столбцах, а следующие байты — столбцы слева направо; бит 0 каждого байта соответствует верхней строке. Например, ı без точки занимает один столбец, а значение 124 (0b01111100) зажигает строки со 2-й по 6-ю; от обычной i она отличается только отсутствием точки сверху. У заглавных Ğ и İ тело буквы сделано на две строки короче (5 строк вместо 7), чтобы осталось место для надстрочного знака. addChar() не копирует данные, а лишь запоминает их адрес, поэтому массивы должны быть глобальными и добавляться после begin(). Коды 0x80–0x85 выбраны потому, что в обычном тексте они не встречаются. Точно так же можно добавить любой символ, например знак градуса, стрелку или сердечко.

Пример часов: цифровые часы с RTC-модулем

Модуль часов реального времени DS1307
RTC-модуль DS1307: благодаря батарейке-таблетке продолжает отсчитывать время, даже когда Arduino выключена; подключается к шине I2C через пины SDA и SCL.

Счётчик millis() у Arduino сбрасывается при пропадании питания, а из-за керамического резонатора на Uno может уходить на несколько минут в сутки. Для настоящих часов используй модуль часов реального времени (RTC). Подключи модуль DS1307 к шине I2C (SDA → A4, SCL → A5, VCC → 5V, GND → GND); MAX7219 использует пины SPI, так что модули друг другу не мешают. Для кода установи из «Менеджера библиотек» библиотеку RTClib от Adafruit.

C++ (Arduino IDE)
1// MAX7219 + DS1307 RTC ile dijital saat / Digital clock with MAX7219 + DS1307 RTC
2#include <MD_Parola.h>
3#include <MD_MAX72xx.h>
4#include <SPI.h>
5#include <RTClib.h>                         // Adafruit RTClib
6
7#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
8#define MAX_DEVICES 4
9#define CS_PIN 10
10
11MD_Parola display = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
12RTC_DS1307 rtc;                             // SDA = A4, SCL = A5
13
14char timeText[8];                           // "12:34" + sonlandırıcı / + terminator
15uint8_t lastSecond = 99;
16
17void setup() {
18  display.begin();
19  display.setIntensity(2);
20  display.setTextAlignment(PA_CENTER);      // print() bu hizalamayı kullanır / print() uses this alignment
21  if (!rtc.begin()) {
22    display.print("RTC?");                  // modül bulunamadı / module not found
23    while (true) {}
24  }
25  if (!rtc.isrunning()) {
26    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));   // ilk kurulumda derleme saati / compile time on first run
27  }
28}
29
30void loop() {
31  DateTime now = rtc.now();
32  if (now.second() != lastSecond) {         // saniyede bir güncelle / update once a second
33    lastSecond = now.second();
34    // ':' ve boşluk aynı genişlikte, yazı kaymaz / ':' and space are equally wide, nothing shifts
35    sprintf(timeText, (lastSecond % 2) ? "%02d %02d" : "%02d:%02d", now.hour(), now.minute());
36    display.print(timeText);
37  }
38}

Скетч раз в секунду читает время и мигает двоеточием. В стандартном шрифте двоеточие (:) и пробел одной ширины (2 столбца), поэтому цифры не прыгают. При первом запуске RTC выставляется на момент компиляции скетча. DS1307 может уходить примерно на минуту в месяц; для более точных часов возьми модуль DS3231, используй RTC_DS3231 вместо RTC_DS1307 и замени условие if (!rtc.isrunning()) на if (rtc.lostPower()). Благодаря кварцу с температурной компенсацией DS3231 уходит лишь примерно на минуту в год. Ещё дисплей можно разделить на две зоны и показывать на одной время, а на другой температуру.

Анимация: попиксельное рисование с MD_MAX72XX

Чтобы выйти за рамки бегущей строки, можно напрямую использовать библиотеку MD_MAX72XX. Она рассматривает всю цепочку как один пиксельный дисплей: у модуля 4-в-1 32 столбца и 8 строк. setPoint(row, column, state) включает или выключает один светодиод, setColumn() записывает сразу все 8 бит столбца, а clear() очищает дисплей. Начало координат (0, 0) — верхний правый угол; номера столбцов растут влево, номера строк — вниз. Код ниже рисует мячик, который отскакивает от краёв дисплея:

C++ (Arduino IDE)
1// MD_MAX72XX ile seken top animasyonu / Bouncing ball animation with MD_MAX72XX
2#include <MD_MAX72xx.h>
3#include <SPI.h>
4
5#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
6#define MAX_DEVICES 4
7#define CS_PIN 10
8
9MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
10
11int x = 0, y = 0;                           // topun konumu (sütun, satır) / ball position (column, row)
12int dx = 1, dy = 1;                         // hareket yönü / direction of travel
13
14void setup() {
15  mx.begin();
16  mx.control(MD_MAX72XX::INTENSITY, 2);
17  mx.clear();
18}
19
20void loop() {
21  const int lastCol = mx.getColumnCount() - 1;          // 4 modülde 31 / 31 with 4 modules
22  mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::OFF);      // çizim bitene kadar gönderme / hold updates while drawing
23  mx.setPoint(y, x, false);                             // eski konumu sil / erase the old position
24  x += dx;
25  y += dy;
26  if (x <= 0 || x >= lastCol) dx = -dx;                 // kenara çarpınca dön / bounce off the edge
27  if (y <= 0 || y >= 7) dy = -dy;
28  mx.setPoint(y, x, true);                              // yeni konumu yak / light the new position
29  mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::ON);       // hepsini tek seferde gönder / send everything at once
30  delay(40);
31}

В каждом кадре сначала отключается автоматическое обновление (UPDATE), стирается старый пиксель и рисуется новый, затем UPDATE снова включается, и изменения уходят в модули за один раз. Так на дисплее не появляется недорисованный кадр. Тем же способом можно нарисовать кубик, смайлики, игру «Змейка» или индикатор уровня звука. В примерах MD_MAX72XX есть и готовые анимации: Pac-Man, глаза робота и песочные часы. При работе с MD_Parola к тем же функциям рисования можно добраться через getGraphicObject().

Сообщения через интернет с ESP32: бегущая строка с телефона

С ESP32 бегущую строку можно сделать беспроводной. Код ниже подключается к домашней сети, сначала прокручивает на дисплее свой IP-адрес, а затем показывает время, полученное из интернета (NTP). Открой этот IP-адрес в браузере телефона, подключённого к той же сети Wi-Fi, и появится текстовое поле; введённое сообщение начнёт бежать со следующего круга и будет чередоваться с часами. Модуль подключается к пинам VSPI, как в таблице: CLK к GPIO 18, DIN к GPIO 23 и CS к GPIO 5. Если ESP32 пока незнакома, сначала прочитай Что такое ESP32?.

C++ (Arduino IDE)
1// ESP32: telefondan mesaj + internet saati / ESP32: message from your phone + internet time
2#include <WiFi.h>
3#include <WebServer.h>
4#include <time.h>
5#include <MD_Parola.h>
6#include <MD_MAX72xx.h>
7#include <SPI.h>
8
9#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
10#define MAX_DEVICES 4
11#define CS_PIN 5                            // VSPI: CLK = GPIO 18, DIN = GPIO 23
12
13const char* ssid = "AgAdi";                 // Wi-Fi adın / your Wi-Fi name
14const char* password = "Sifre123";          // Wi-Fi şifren / your Wi-Fi password
15
16MD_Parola display = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
17WebServer server(80);
18
19char message[128];
20char nextMessage[128];
21char clockText[8] = "--:--";
22bool newMessage = false;
23bool showClock = false;
24
25const char PAGE[] = R"rawliteral(<!DOCTYPE html><html><head><meta charset="utf-8">
26<meta name="viewport" content="width=device-width"><title>MAX7219</title></head><body>
27<h2>Kayan yazı / Scrolling text</h2>
28<form action="/"><input name="m" maxlength="100"> <button>Gönder / Send</button></form>
29</body></html>)rawliteral";
30
31void handleRoot() {
32  if (server.hasArg("m")) {                 // formdan gelen mesaj / message from the form
33    server.arg("m").toCharArray(nextMessage, sizeof(nextMessage));
34    newMessage = true;
35  }
36  server.send(200, "text/html; charset=utf-8", PAGE);
37}
38
39void setup() {
40  display.begin();
41  display.setIntensity(3);
42  display.print("WiFi..");
43  WiFi.begin(ssid, password);
44  while (WiFi.status() != WL_CONNECTED) delay(500);
45  configTime(3 * 3600, 0, "pool.ntp.org");  // Türkiye UTC+3 / Turkey is UTC+3
46  snprintf(message, sizeof(message), "IP: %s", WiFi.localIP().toString().c_str());
47  server.on("/", handleRoot);
48  server.begin();
49  display.displayText(message, PA_LEFT, 40, 0, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
50}
51
52void loop() {
53  server.handleClient();
54  if (display.displayAnimate()) {           // önceki gösterim bitti / the previous show has ended
55    showClock = !showClock;
56    if (newMessage) {                       // yeni mesaj sırayı keser / a new message jumps the queue
57      strlcpy(message, nextMessage, sizeof(message));
58      newMessage = false;
59      showClock = false;
60    }
61    if (showClock) {
62      struct tm t;
63      if (getLocalTime(&t, 10)) strftime(clockText, sizeof(clockText), "%H:%M", &t);
64      display.displayText(clockText, PA_CENTER, 40, 3000, PA_SCROLL_DOWN, PA_SCROLL_UP);
65    } else {
66      display.displayText(message, PA_LEFT, 40, 0, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
67    }
68  }
69}

handleRoot() не отправляет сообщение из формы сразу на дисплей, а откладывает его в nextMessage. Сообщение копируется только после окончания текущей анимации (когда displayAnimate() возвращает true), поэтому текст, который читает MD_Parola, никогда не меняется на середине. Значение 3 × 3600 в configTime() — часовой пояс UTC+3, принятый в Турции (как и в Москве); для Великобритании замени эту строку на configTzTime("GMT0BST,M3.5.0/1,M10.5.0", "pool.ntp.org"), которая учитывает и переход на летнее время. Для сообщений с диакритикой добавь в этот скетч строки addChar() и функцию utf8ToFont() из примера выше и замени строку strlcpy() на utf8ToFont(nextMessage, message, sizeof(message)). Про время из интернета и веб-панели в реальном времени читай в статьях Часы на ESP32 по NTP и Веб-сервер и WebSocket на ESP32.

MAX7219 с Raspberry Pi Pico и MicroPython

На Pico с MicroPython удобнее всего скопировать на плату через Thonny открытый драйвер max7219.py (micropython-max7219). Драйвер использует модуль MicroPython framebuf, поэтому функции рисования вроде text(), pixel(), line() и rect() доступны сразу, а на модулях 4-в-1 FC-16 изображение выводится в правильной ориентации. Питай модуль от VBUS (5 V) и подключи DIN к GP19, CLK к GP18 и CS к GP17. MicroPython занимает ещё и пин MISO шины SPI0 (GP16), так что не используй этот пин ни для чего другого. Шрифт framebuf размером 8x8 пикселей и содержит только ASCII, поэтому модуль 4-в-1 показывает по 4 буквы, а буквы с диакритикой не отображаются. Про настройку читай Что такое Raspberry Pi Pico? и Основы MicroPython.

MicroPython
1# Pico + 4'lü MAX7219 modül: kayan yazı / Pico + 4-in-1 MAX7219 module: scrolling text
2from machine import Pin, SPI
3import time
4import max7219                        # max7219.py Pico'da olmalı / must be on the Pico
5
6# GP18 = SCK -> CLK, GP19 = MOSI -> DIN, GP17 -> CS; GP16 (MISO) boş kalır / GP16 (MISO) stays free
7spi = SPI(0, baudrate=5_000_000, polarity=0, phase=0, sck=Pin(18), mosi=Pin(19))
8display = max7219.Matrix8x8(spi, Pin(17, Pin.OUT), 4)
9display.brightness(3)                 # parlaklık 0–15 / brightness 0–15
10
11def scroll(text, delay_ms=35):
12    width = len(text) * 8             # her harf 8 piksel / each letter is 8 px wide
13    for x in range(32, -width, -1):   # sağdan girip solda kaybolur / enters right, leaves left
14        display.fill(0)
15        display.text(text, x, 0, 1)
16        display.show()
17        time.sleep_ms(delay_ms)
18
19while True:
20    scroll("BlueGrays + Pico")
21    display.fill(0)
22    display.text("Pico", 0, 0, 1)     # 4 harf tam sığar / exactly 4 letters fit
23    display.show()
24    time.sleep(1)
25    display.fill(0)
26    display.rect(0, 0, 32, 8, 1)      # ekran çerçevesi / display frame
27    display.show()
28    time.sleep_ms(500)

Идеи проектов на MAX7219

  • Табло с бегущей строкой: собери информационное табло для магазина, класса или двери комнаты и меняй сообщение с телефона через модуль Bluetooth HC-05.
  • Часы, календарь и температура: по очереди показывай время и дату с RTC, а также температуру и влажность с датчика DHT11.
  • Интернет-часы и погода: прокручивай время NTP и температуру из погодного API с помощью ESP32.
  • Табло для счёта: сделай дисплей с двумя зонами для матча по настольному теннису или баскетболу с управлением от ИК-пульта.
  • Мини-игры: «Змейка», Pong или игра с уклонением от препятствий на дисплее 32x8 с джойстиком; больше идей в статье Игровые проекты на Arduino.
  • Индикатор уровня звука: выводи показания датчика звука в виде столбчатой диаграммы из 32 столбцов.
  • Анимационная панель 32x32: поставь четыре модуля 4-в-1 друг на друга и запускай анимации вроде дождя, звёзд и фейерверков.

Частые ошибки и как их исправить

СимптомВероятная причинаРешение
Не горит ни один светодиодНет питания, провода подключены к стороне OUT модуля или пин CS не совпадает с кодомПодключи провода к стороне IN с надписью DIN, проверь CS_PIN и питание 5 V
Все светодиоды горят в полную силу и не меняютсяДанные не доходят: DIN и CLK перепутаны или микросхема застряла в тестовом режимеПодключи DIN к MOSI, CLK к SCK, затем отключи и снова подай питание на модуль
Буквы зеркальные, перевёрнутые или повёрнуты на 90 градусовНеправильный HARDWARE_TYPEПопробуй FC16_HW на модуле 4-в-1 и GENERIC_HW на одиночном модуле
Весь текст перевёрнут и бежит слева направоМодуль установлен наоборот, вход DIN слеваПоверни модуль на 180 градусов: вход DIN должен быть справа
Работает только один блок или текст выглядит обрезаннымНеправильный MAX_DEVICES или обрыв связи между модулямиЗадай MAX_DEVICES равным числу блоков и проверь провода OUT → IN
Arduino перезагружается или изображение мерцает на большой яркостиПревышен лимит USB в 500 mA, и напряжение проседаетИспользуй внешний блок питания 5 V 2 A, объедини земли и снизь яркость
Случайные точки и искажённые символы на ESP32Сигнал 3,3 V ниже порога HIGH в 3,5 V, а провода длинныеПодними уровень с помощью 74HCT125 и укороти провода
Буквы с диакритикой выводятся бессмысленными значкамиБайты UTF-8 идут прямо в шрифтИспользуй подход с utf8ToFont() и addChar()
Бегущая строка запинается и дёргаетсяВ loop() есть delay() или долгая задачаВызывай displayAnimate() часто, а ожидания делай через millis()
Когда что-то не работает, начни с простейшего теста: оставь в коде только display.begin() и display.print("8888"). Если не горит ни один светодиод, проблема в подключении или питании; проверь мультиметром, что между пинами VCC и GND модуля есть 4,5–5 V. Если светодиоды горят, но цифры искажены, дело в типе оборудования; если работает только первый блок, смотри MAX_DEVICES или соединения между модулями.

MAX7219 — один из самых дешёвых способов получить эффектный дисплей всего на нескольких проводах: микросхема берёт на себя сканирование, ограничение тока и яркость, так что можно сосредоточиться на том, что показывать. Начни с иконок на одиночном модуле 8x8, затем переходи к бегущей строке, часам и беспроводным сообщениям через ESP32 на модуле 4-в-1. Если нужны цветные светодиоды с индивидуальным управлением, хороший выбор — светодиоды WS2812B NeoPixel, а для проекта, который показывает только цифры, — 7-сегментный индикатор. В приложении BlueGrays есть проекты на MAX7219: точечные часы с календарём, метеостанция, анимации 32x8 и бегущая строка — пошагово, со схемами подключения и кодом.

Частые вопросы

Почему текст на модуле MAX7219 выглядит перевёрнутым или зеркальным?

Производители подключают матрицу 8x8 к MAX7219 по-разному, а библиотека компенсирует это настройкой HARDWARE_TYPE. Большинство синих модулей 4-в-1, которые продаются сегодня, правильно показывают с FC16_HW, а одиночные зелёные — с GENERIC_HW. Если изображение всё ещё неправильное, попробуй также PAROLA_HW и ICSTATION_HW: меняется всего одна строка кода.

Сколько модулей MAX7219 можно соединить в цепочку?

У самой микросхемы ограничения нет, а поскольку MD_MAX72XX хранит количество модулей в одном байте, программно допускается до 255. На практике предел задают питание и качество сигнала: каждые 4–8 блоков заново подводи к цепочке 5 V и GND, делай провода короткими и ставь на каждый модуль конденсаторы 10 µF и 100 nF. Чем длиннее цепочка, тем дольше длится каждое обновление и тем больше ОЗУ оно занимает.

Работает ли MAX7219 с ESP32 на 3,3 V?

MAX7219 нужно питание не меньше 4 V, поэтому питай модуль от пина 5V (VIN) платы ESP32. Даташит при питании 5 V требует для уровня HIGH 3,5 V; сигналы ESP32 на 3,3 V работают на большинстве модулей, но они ниже этой границы. Для надёжной работы поставь на линии DIN, CLK и CS преобразователь уровней, например 74HCT125.

Какой ток потребляет светодиодная матрица MAX7219?

Одиночный модуль 8x8 со всеми горящими светодиодами на полной яркости потребляет около 330 mA, а модуль 4-в-1 8x32 — до 1,3 A. В режиме бегущей строки горит только часть светодиодов, а яркость обычно держат от 2 до 7, поэтому модуль 4-в-1 обычно потребляет около 100–250 mA. В режиме отключения каждая микросхема потребляет всего около 150 µA.

Как вывести на MAX7219 буквы с диакритикой или специальные символы?

Стандартный шрифт MD_Parola содержит символы Latin-1, например é, ñ, ä, ö, ü и ß, но Arduino IDE хранит их как два байта UTF-8. Перед выводом переведи текст в коды шрифта небольшой функцией. Символы вне Latin-1 можно нарисовать вручную и добавить через addChar().

Может ли MAX7219 управлять 7-сегментным индикатором?

Да. Изначально MAX7219 и создавался для 7-сегментных индикаторов с общим катодом на 8 разрядов, и у него есть режим BCD, в котором он сам декодирует цифры. Готовые 8-разрядные модули индикаторов на MAX7219 легко использовать с функцией setDigit() библиотеки LedControl. Индикаторы с общим анодом напрямую к этой микросхеме подключить нельзя.

Все проекты
Часы и календарь на матрице (MAX7219, DS1307)
#84Средний
Premium

Часы и календарь на матрице (MAX7219, DS1307)

Светящееся табло для стола или стены, которое показывает время и дату бегущей строкой. На светодиодном дисплее 8x32 крупные цифры часов, а две точки между ними мигают каждую секунду; в начале каждой м

5.0(37)
59
Часы, календарь и метеостанция на матрице (MAX7219, DS1307)
#85Средний
Premium

Часы, календарь и метеостанция на матрице (MAX7219, DS1307)

Табло с бегущей строкой для стола или стены, которое по очереди показывает время, дату и погоду в комнате. На светодиодном дисплее 8x32 сначала видна температура в градусах Цельсия и Фаренгейта, затем

5.0(50)
66
Анимация и текст на 4 матрицах 32x8
#90Средний
Premium

Анимация и текст на 4 матрицах 32x8

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

4.6(40)
66
4 матрицы 32x8 — набор из 6 анимаций
#91Средний
Premium

4 матрицы 32x8 — набор из 6 анимаций

Четыре светодиодные панели 32x8, сложенные друг на друга, образуют квадратный экран, на котором по очереди идут шесть анимаций: падающий цифровой дождь, растущая спираль, клетки, которые сами рождаютс

4.5(35)
48
Пиксельная змейка на матрице 8x8
#88Продвинутый
Premium

Пиксельная змейка на матрице 8x8

Собери классическую игру «Змейка» своими руками и сыграй в неё. На цветной светодиодной матрице 8x8 ты джойстиком управляешь зелёной змейкой и ешь красные яблоки; с каждым яблоком змейка растёт, немно

4.7(39)
57
Точное время из интернета через NTP на ESP32
#128Продвинутый
Premium

Точное время из интернета через NTP на ESP32

ESP32 подключается к Wi-Fi и получает точное время с точностью до миллисекунд от серверов NTP (Network Time Protocol). Благодаря функции configTzTime() автоматически применяется часовой пояс Турции (TRT, UTC+3), а в странах с летним временем переходы обрабатываются автоматически по строке POSIX TZ. Время выводится в монитор порта и на необязательный I2C LCD. configTzTime() запускает службу SNTP, которая периодически пересинхронизирует часы.

4.5(13)
21
Цифровые часы на ESP32 и DS1307 RTC (I2C + LCD)
#120Средний
Premium

Цифровые часы на ESP32 и DS1307 RTC (I2C + LCD)

В этом проекте время и дата из модуля часов реального времени (RTC) DS1307 выводятся на I2C LCD-экран 16x2. DS1307 — RTC с резервной батарейкой, который хранит время даже при выключенной ESP32. Оба модуля делят одну шину I2C (SDA/SCL), и ты узнаёшь, как управлять несколькими устройствами всего по двум сигнальным проводам. Проект даёт практические знания о протоколе I2C, работе модуля RTC и динамическом выводе данных на LCD-экран.

4.6(26)
40
Цифровые часы на 4-разрядном семисегментном индикаторе
#48Средний
Premium

Цифровые часы на 4-разрядном семисегментном индикаторе

Светящиеся цифровые часы из четырёх разрядов, почти как на кухонной духовке. Модуль часов DS1302 хранит время, а Arduino каждую секунду читает часы и минуты и показывает их на четырёхразрядном индикат

4.8(56)
73