Arduino — это не только про соединение проводов и написание скетчей, но и про умение подружить железки, которые, казалось бы, созданы друг для друга, но упорно не желают работать вместе. Эта статья — история одной такой дружбы. Рассматривается решение, написанное и протестированное на основе товара «Умный дом на базе Arduino. Большой набор + книга», который вы можете приобрести в нашем Интернет-магазине.

Вступление

В рамках «импортозамещения» мы решили модернизировать наш популярный набор  «Умный дом на базе Arduino». Причины были чисто практические:

  • Замена Bluetooth-модуля HM-10 на HC-06, т.к. модуль HM-10 теперь отсутствует в «Едином реестре нотификаций о характеристиках шифровальных (криптографических) средств и товаров», поэтому его ввоз в Россию требует оформления специальных сертификатов. Это долго и дорого. А модуль HC-06 в реестре пока имеется и есть у нас в достаточном количестве. А при желании, пользователь может купить HM-10 самостоятельно в магазине или на AliExpress — их продажа не запрещена, проблемы с ввозом оптовых партий.

  • Замена приложения RemoteXY на Virtuino.  RemoteXY невозможно купить в Google Play из России из-за блокировки платежей, а возможности бесплатной версии стали существенно ограничены.

Рис. 1. Набор “Умный дом на базе Arduino. Большой набор + книга
Рис. 1. Набор “Умный дом на базе Arduino. Большой набор + книга

Казалось бы, простая замена компонентов. Но когда мы начали тестировать все эксперименты из набора, нас ждал сюрприз: При одновременной работе RFID-модуля RC522  и светодиодной матрицы 8×8 на MAX7219 матрица зависала при поднесении карточки RFID. По отдельности оба устройства работали идеально, но вместе — отказывались дружить.

Рис. 2. RFID-модуль RC522
Рис. 2. RFID-модуль RC522
Рис. 3. Светодиодная матрица 8x8 с модулем MAX7219
Рис. 3. Светодиодная матрица 8x8 с модулем MAX7219

Самое интересное: этот же выставочный макет успешно работал весной прошлого года и с тех пор мы его не включали. Железо осталось тем же — те же модули, та же Arduino, те же провода. Что же изменилось? Мы обновили Arduino IDE до последней версии, а вместе с ней — обновились и библиотеки. Нашли информацию, что библиотека по управлению матрицей — LedControl.h  (версия 1.06) — не менялась с 2015 года (https://wayoda.github.io/LedControl/index.html), а вот библиотека SPI, входящая в состав ядра Arduino, вполне могла измениться в сторону улучшения взаимодействия устройств SPI. Скорее всего, именно обновление SPI-библиотеки привело к тому, что после работы RFID пины оставались в состоянии, непригодном для работы матрицы.

Мы перерыли весь интернет в поисках решения. Вопросы по этой теме встречаются на форумах, но внятных ответов мы не нашли. Кто-то советовал ставить дополнительные конденсаторы, кто-то — переходить на I2C, кто-то — разводить питание. Ничего не помогало. Пришлось разбираться самостоятельно.

Почему так происходит, и как это исправить — читайте далее.

В чём проблема?

Давайте посмотрим на стандартную схему подключения в наборе (рис. 4, табл. 1).

Рис. 4. «Стандартное» подключение модуля RFID и светодиодной матрицы 8x8 MAX7219
Рис. 4. «Стандартное» подключение модуля RFID и светодиодной матрицы 8x8 MAX7219

Пин Arduino

RFID-модуль RC522

Матрица на MAX7219

Сигнал SPI

Описание

D9

RST

RST (Reset)

Сброс модуля. Не является сигналом SPI, но необходим для инициализации RC522. При подаче низкого уровня (LOW) модуль сбрасывается.

D10

SDA

CS

SS (Slave Select)

Выбор ведомого устройства. Когда сигнал низкий (LOW), модуль понимает, что к нему обращаются.

Может обозначаться как SS, CS (Chip Select), SDA (на RC522).

D11

MOSI

DIN

MOSI (Master Out Slave In)

Выход мастера, вход ведомого. По этой линии Arduino передаёт данные на устройство.

Может обозначаться как MOSI, DI (Data In), DIN, SI (Serial In).

D13

SCK

CLK

SCK (Serial Clock)

Тактовый сигнал (импульсы синхронизации). Генерируется мастером (Arduino). Все устройства на шине SPI слушают этот сигнал. Может обозначаться как: SCK, SCLK, CLK.

D12

MISO

MISO (Master In Slave Out)

Вход мастера, выход ведомого. По этой линии устройство передаёт данные в Arduino. Для матрицы MAX7219 этот сигнал не используется (матрица только принимает данные, но не отправляет).

Может обозначаться как: MISO, DO (Data Out), DON, SO (Serial Out).

Таблица 1. Назначение контактов при подключении подключение
модуля RFID и светодиодной матрицы 8x8 MAX7219 к Arduino Uno

Обратите внимание: оба устройства используют одни и те же линии D11 (MOSI/DIN) и D13 (SCK/CLK). Но такое подключение (общие линии MOSI и SCK, раздельные линии CS/SS) является абсолютно стандартным для организации шины SPI с несколькими ведомыми устройствами. Как подробно описано в статье «Введение в интерфейс SPI» (из книги Т. Иго “ Умные вещи: Arduino, датчики и сети для связи устройств”) на нашем сайте BHV (https://bhv.ru/wikibook/vvedenie-v-interfejs-spi), ��нтерфейс SPI позволяет подключить к шине несколько устройств под управлением одного главного контроллера, объединяя все линии, кроме выбора ведомого (CS/SS). Это нормальная и правильная схема включения (рис. 5).

Рис. 5. Подключение нескольких устройств по протоколу SPI
Рис. 5. Подключение нескольких устройств по протоколу SPI

Если всё правильно, то почему наша схема не работает?

Казалось бы, подключение выполнено по всем правилам SPI-шины: линии MOSI и SCK общие, CS раздельные. Именно так рекомендуется подключать несколько устройств SPI. Так почему же матрица зависает?

Всё дело в том, КАК именно устройства используют эти линии. Тут и оказалась зарыта «та самая собака», которая не давала одновременно работать этим двум устройствам RFID-модуль RC522 использует аппаратный SPI, а MAX7219 — программный (см. табл. 2).

Устройство

Тип SPI

Как работает

RFID-модуль RC522

Аппаратный

Управляется специальным оборудованием внутри микроконтроллера. После инициализации аппаратный SPI «захватывает» пины D11 (MOSI) и D13 (SCK) — они перестают подчиняться обычным командам digitalWrite().

Матрица на MAX7219

Программный

Библиотека LedControl управляет пинами вручную через digitalWrite (функции вроде shiftOut()). Она ожидает, что пины находятся в обычном режиме цифровых выходов и послушно изменят состояние по команде.

Таблица 2. Аппаратный и программный SPI

В этом и заключается конфликт. Пока RFID молчит, матрица прекрасно работает — пины свободны. Но как только RFID выполняет чтение карты, его библиотека настраивает аппаратный SPI, и пины D11, D13 переходят под аппаратное управление. Матрица продолжает «стучаться» в эти пины через digitalWrite, но аппаратный SPI игнорирует её команды. Результат — полное зависание изображения.

Таким образом, проблема не в схеме, а в конфликте двух разных способов управления одними и теми же пинами.

А что, если перейти на I2C или UART?

Согласно даташиту микросхема MFRC522, установленная на модуле RFID поддерживает I2C и UART (рис. 6)! Может, просто переключить её в этот режим и забыть о конфликте?

Рис. 6. Упрощенная структурная схема MFRC522
Рис. 6. Упрощенная структурная схема MFRC522

Технически это возможно, но, как говорится, дьявол в деталях. На форуме Arduino народ уже давно обсуждает эту тему. Результаты экспериментов — настоящий коктейль из энтузиазма и разочарования.

Что предлагают энтузиасты:

  • Нужно физически разорвать две дорожки на плате (к выводам 1 и 32 микросхемы)

  • Затем подпаять перемычки, чтобы задать нужный режим

  • Некоторые даже умудряются подсунуть кусочек каптоновой ленты под выводы микросхемы перед пайкой, чтобы не резать дорожки

Что получается на практике:

  • У кого‑то действительно получается заставить модуль работать по I2C

  • У других модуль просто перестаёт определяться

  • Третьи возвращают всё обратно и SPI снова работает

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

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

Поэтому мы оставили эксперименты с I2C смелым энтузиастам, а сами пошли искать решение в родной для этих модулей среде — мире SPI. И, как видите, нашли!

Простое решение: развести по разным пинам

Самое очевидное решение — разнести устройства по разным пинам. Например, матрицу можно подключить на другие цифровые выходы:

Назначение

Обычный пин

Новый пин

DIN (данные)

11

7

CLK (тактирование)

13

6

CS (выбор кристалла)

8

8 (можно оставить)

Вот как это выглядит на схеме подключения (рис. 7).

Рис. 7. Подключение матрицы и модуля RFID к разным пинам Arduino
Рис. 7. Подключение матрицы и модуля RFID к разным пинам Arduino

Листинг 1. Код для такого подключения:

// Новые пины для матрицы
#define MATRIX_DIN 7
#define MATRIX_CLK 6
#define MATRIX_CS 8

LedControl matrix = LedControl(MATRIX_DIN, MATRIX_CLK, MATRIX_CS, 1);

И всё будет работать! Но есть один недостаток — мы занимаем два дополнительных пина. В проекте «Умный дом» пины и так все заняты, а тут ещё теряем ценный ШИМ-выход D6, который можно было бы использовать для сервопривода, открывающего дверь.

Элегантное решение: SPI.end() приходит на помощь

Для отладки мы написали специальный тестовый скетч, который делает всего две вещи:

1.     Тестирует матрицу — при запуске зажигает на матрице все светодиоды, а затем последовательно показывает стрелку, счастливый и нейтральный смайлы. Это позволяет убедиться, что матрица работает исправно до любого взаимодействия с RFID.

2.     Реагирует на RFID — при поднесении карточки в последовательный порт выводится сообщение «Доступ разрешен» (или «Доступ запрещен»), а на матрице должна загореться стрелка, указывающая путь к двери. После паузы матрица возвращается к нейтральному смайлу.

Этот минималистичный скетч помог нам изолировать проблему и убедиться, что дело именно в конфликте SPI, а не в других частях основного проекта.

После долгих экспериментов мы нашли изящное решение. Оказывается, достаточно перед каждым обращением к матрице полностью отключать аппаратный SPI, и больше его не включать!

Секрет в функции SPI.end(). Она не только отключает SPI, но и переводит пины в исходное состояние. После этого матрицу нужно заново проинициализировать — и она работает как ни в чём не бывало.

Важный момент: после SPI.end() мы не вызываем SPI.begin()!

Библиотека MFRC522 при следующем обращении к RFID сама проверит состояние SPI и при необходимости включит его. Это экономит память и делает код стабильнее.

Алгоритм простой:

1. RFID обнаружил карту и прочитал данные.

2. Выключаем SPI: SPI.end();

3. Делаем небольшую задержку.

4. Заново настраиваем пины матрицы как выходы.

5. Переинициализируем матрицу (shutdown(false), setIntensity, clearDisplay).

6. Показываем нужное изображение (например, стрелку).

7. НЕ включаем SPI обратно — библиотека RFID сделает это сама при следующем вызове.

Код, который работает на 100%

Вот финальная версия, которую мы используем в проекте. Все пины остаются стандартными, ничего переподключать не нужно:

Листинг 2. Файл конфигурации config.h

// Пины остаются стандартными!
#define DIN_PIN 11
#define CLK_PIN 13
#define CS_PIN 8

#define SS_PIN 10
#define RST_PIN 9

// Изображения
const byte smile_happy[8] PROGMEM = {
  B00000000, B00011000, B00100100, B01000010,
  B00000000, B00000000, B00100100, B00000000
};

const byte smile_normal[8] PROGMEM = {
  B00000000, B00000000, B00000000, B01111110,
  B00000000, B00000000, B00100100, B00000000
};

// Стрелка, указывающая на дверь
const byte arrow[8] PROGMEM = {
  0b00111111, 0b00011111, 0b00011111, 0b00111111,
  0b01111111, 0b11111001, 0b01110000, 0b00100000
};

// Код вашей карточки RFID
const String allowedCards[] = {"7C 36 39 A5", "D2 07 A9 1B"};
const int cardCount = 2;

Листинг 3. Основной код

#include <SPI.h>
#include "LedControl.h"
#include "MFRC522.h"
#include "config.h"

LedControl matrix = LedControl(DIN_PIN, CLK_PIN, CS_PIN, 1);
MFRC522 rfid(SS_PIN, RST_PIN);

void setup() {
  Serial.begin(9600);
  
  // Инициализация матрицы
  matrix.shutdown(0, false);
  matrix.setIntensity(0, 8);
  matrix.clearDisplay(0);
  
  // Тест матрицы (по желанию)
  for (int row = 0; row < 8; row++) matrix.setRow(0, row, 0xFF);
  delay(500);
  matrix.clearDisplay(0);
  showSmile(1);
  
  // Инициализация RFID
  SPI.begin();
  rfid.PCD_Init();
}

void loop() {
  if (rfid.PICC_IsNewCardPresent() && rfid.PICC_ReadCardSerial()) {
    
    // Читаем UID карты
    String uid = "";
    for (byte i = 0; i < rfid.uid.size; i++) {
      uid += (rfid.uid.uidByte[i] < 0x10 ? " 0" : " ");
      uid += String(rfid.uid.uidByte[i], HEX);
    }
    uid.toUpperCase();
    uid = uid.substring(1);
    
    bool access = false;
    for (int i = 0; i < cardCount; i++) {
      if (uid == allowedCards[i]) access = true;
    }
    
    rfid.PICC_HaltA();
    
    // *** ВОЛШЕБСТВО: отключаем SPI перед работой с матрицей ***
    SPI.end();
    delay(5);
    
    // Переинициализируем пины матрицы
    pinMode(DIN_PIN, OUTPUT);
    pinMode(CLK_PIN, OUTPUT);
    pinMode(CS_PIN, OUTPUT);
    digitalWrite(CS_PIN, HIGH);
    
    matrix.shutdown(0, false);
    matrix.setIntensity(0, 8);
    matrix.clearDisplay(0);
    
    if (access) {
      Serial.println("Доступ разрешен");
      // Показываем стрелку
      for (int r = 0; r < 8; r++) 
        matrix.setRow(0, r, pgm_read_byte(&arrow[r]));
      delay(2000);
      
      // Здесь можно открыть дверь сервоприводом
      // ...
      
    } else {
      Serial.println("Доступ запрещен");
      for (int i = 0; i < 3; i++) {
        matrix.clearDisplay(0);
        delay(200);
        showSmile(1);
        delay(200);
      }
    }
    
    // Возвращаем обычный смайл
    showSmile(1);
    
    // *** ВАЖНО: НЕ вызываем SPI.begin() здесь! ***
    // Библиотека MFRC522 сама включит SPI при следующем обращении
  }
  
  delay(100);
}

void showSmile(int type) {
  const byte* s = (type == 0) ? smile_happy : smile_normal;
  for (int r = 0; r < 8; r++) 
    matrix.setRow(0, r, pgm_read_byte(&s[r]));
}

Почему это работает?

Весь секрет в функции SPI.end(), которая:

1.     Отключает аппаратный SPI.

2.     Сбрасывает регистры SPI в исходное состояние.

3.     Переводит пины в обычный режим (не периферия).

После этого матрица получает «чистые» пины и может работать с ними через программный SPI сколько угодно. А библиотека MFRC522 «достаточно умна», чтобы при следующем вызове PICC_IsNewCardPresent() проверить состояние SPI и, если нужно, включить его снова. Поэтому явный вызов SPI.begin() не требуется и даже вреден, т.к. приводит к зависанию матрицы.

Преимущества этого решения

  • Не требует переподключения проводов — все остаётся на стандартных пинах.

  • Экономит пины — не нужно занимать дополнительные выходы.

  • Сохраняет ШИМ-пины — D6 и D9 остаются свободными для сервоприводов.

  • Простота — всего несколько строк кода решают проблему.

  • Стабильность — проверено сотнями циклов открытия двери.

  • Экономия памяти — не вызываем лишний раз SPI.begin().

  • Проверено на практике — то, чего не нашли в интернете, нашли сами.

Заключение

Если вы столкнулись с тем, что в вашем проекте RFID и матрица MAX7219 не хотят работать вместе — не спешите перепаивать провода, заказывать другие модули или переписывать программный код. Мы перерыли весь интернет в поисках решения, но внятных ответов не нашли. Пришлось разбираться самим. И вот к чему мы пришли после долгих экспериментов.

Просто используйте SPI.end() перед работой с матрицей и не вызывайте SPI.begin() после. Это простое и элегантное решение, которое мы нашли методом проб и ошибок, и теперь делимся им с вами.

Важное замечание

Мы не испытывали десятки модулей и матрицы, а только «подружили» те, которые были у нас под рукой и перестали работать после обновления IDE. Поэтому не можем на 100% гарантировать, что с вашими модулями проблем не будет — производители могут использовать разные версии микросхем и компонентов. Однако уверенность есть, и очень надеемся, что наше решение поможет и вам!

Наш модернизированный набор «Умный дом на базе Arduino. Большой набор + книга» с HC-06 и Virtuino отлично работает, дверь открывается по RFID-картам и по команде со смартфона, на матрице загорается стрелка, а студенты не тратят часы на поиск неочевидной проблемы.

Рис. 8. Работа модернизированного домика в действии. Сверху: режим ожидания — на матрице «весёлый смайл». Снизу: момент поднесения RFID-метки — на матрице загорается стрелка, указывающая путь к открытой двери. Матрица и RFID работают одновременно без зависаний благодаря использованию SPI.end().
Рис. 8. Работа модернизированного домика в действии. Сверху: режим ожидания — на матрице «весёлый смайл». Снизу: момент поднесения RFID-метки — на матрице загорается стрелка, указывающая путь к открытой двери. Матрица и RFID работают одновременно без зависаний благодаря использованию SPI.end().

Надеемся, эта статья сэкономит ваше время и нервы тоже!

Будем благодарны за ваши идеи и решения! Если вы нашли другой способ подружить эти устройства (например, по протоколу I2С или UART),  или столкнулись с похожей проблемой — поделитесь опытом в комментариях. Вместе мы сможем найти ещё более элегантные решения.

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