Назад

Осваиваем Serial, I2C и SPI на Arduino с Velxio

Введение

Протоколы связи — основа встраиваемых систем. Читаете ли вы датчик, общаетесь с SD-картой или выводите отладочные сообщения — вы используете один из трех основных протоколов: Serial (UART), I2C (TWI) или SPI. В этом учебнике мы запустим все три одновременно на Arduino Uno с помощью браузерного симулятора Velxio. Вы увидите, как сканировать шину I2C, читать/записывать EEPROM, выполнять передачи SPI и выводить всё через Serial.

Схема мультипротокольного подключения на холсте симулятора Velxio

Обзор схемы

В этом примере используется только сама плата Arduino Uno — внешние компоненты не требуются. Встроенные периферийные устройства Arduino предоставляют всё необходимое:

Подключение не требуется, так как все соединения внутренние для Arduino. Холст Velxio показывает Uno с подписями пинов, и вы можете просмотреть виртуальные компоненты, открыв панель выбора компонентов.

Модальное окно выбора компонентов Velxio с просмотром категорий

Разбор кода

Давайте разберем скетч шаг за шагом.

Библиотеки и определения

#include <Wire.h>
#include <SPI.h>

#define DS1307_ADDR 0x68
#define EEPROM_ADDR 0x50
#define SS_PIN 10

Мы подключаем библиотеку Wire для I2C и библиотеку SPI для SPI. Определены два адреса I2C: 0x68 для RTC DS1307 (хотя физически он отсутствует, мы будем его искать) и 0x50 для EEPROM 24Cxx. Пин выбора ведомого SPI установлен на цифровой пин 10.

Вспомогательные функции

byte bcdToDec(byte val) {
  return ((val >> 4) * 10) + (val & 0x0F);
}

void readRTC(byte &hr, byte &min, byte &sec) {
  Wire.beginTransmission(DS1307_ADDR);
  Wire.write(0x00);
  Wire.endTransmission();
  Wire.requestFrom(DS1307_ADDR, 3);
  sec = bcdToDec(Wire.read() & 0x7F);
  min = bcdToDec(Wire.read());
  hr  = bcdToDec(Wire.read() & 0x3F);
}

Функция bcdToDec преобразует двоично-десятичный код (BCD) в десятичный. readRTC считывает секунды, минуты и часы из RTC DS1307. Маски & 0x7F и & 0x3F очищают неиспользуемые биты.

void writeEEPROM(byte reg, byte value) {
  Wire.beginTransmission(EEPROM_ADDR);
  Wire.write(reg);
  Wire.write(value);
  Wire.endTransmission();
  delay(5);
}

byte readEEPROM(byte reg) {
  Wire.beginTransmission(EEPROM_ADDR);
  Wire.write(reg);
  Wire.endTransmission();
  Wire.requestFrom(EEPROM_ADDR, 1);
  return Wire.available() ? Wire.read() : 0xFF;
}

Эти функции записывают и читают один байт из EEPROM I2C по адресу 0x50. delay(5) гарантирует, что EEPROM успеет завершить цикл записи.

byte spiTransfer(byte data) {
  digitalWrite(SS_PIN, LOW);
  byte result = SPI.transfer(data);
  digitalWrite(SS_PIN, HIGH);
  return result;
}

Эта функция выполняет полнодуплексную передачу SPI: опускает SS, отправляет байт, получает ответ и поднимает SS.

Setup

void setup() {
  Serial.begin(9600);
  Wire.begin();
  pinMode(SS_PIN, OUTPUT);
  digitalWrite(SS_PIN, HIGH);
  SPI.begin();

  Serial.println("===================================");
  Serial.println(" Multi-Protocol Demo");
  Serial.println(" Serial (USART) + I2C (TWI) + SPI");
  Serial.println("===================================");
  Serial.println();

  // ── I2C: Scan bus ──
  Serial.println("[I2C] Scanning bus...");
  int found = 0;
  for (byte addr = 1; addr < 127; addr++) {
    Wire.beginTransmission(addr);
    if (Wire.endTransmission() == 0) {
      Serial.print("  Found device at 0x");
      if (addr < 16) Serial.print("0");
      Serial.println(addr, HEX);
      found++;
    }
  }
  Serial.print("  ");
  Serial.print(found);
  Serial.println(" device(s) on I2C bus.");
  Serial.println();

  // ── I2C: Write/read EEPROM ──
  Serial.println("[I2C] EEPROM write/read test:");
  writeEEPROM(0, 42);
  writeEEPROM(1, 99);
  byte v0 = readEEPROM(0);
  byte v1 = readEEPROM(1);
  Serial.print("  Wrote 42, read ");
  Serial.print(v0);
  Serial.println(v0 == 42 ? " [OK]" : " [FAIL]");
  Serial.print("  Wrote 99, read ");
  Serial.print(v1);
  Serial.println(v1 == 99 ? " [OK]" : " [FAIL]");
  Serial.println();

  // ── SPI: Transfer test ──
  Serial.println("[SPI] Transfer test:");
  byte spiData[] = {0xAA, 0x55, 0x42};
  for (int i = 0; i < 3; i++) {
    byte rx = spiTransfer(spiData[i]);
    Serial.print("  TX=0x");
    if (spiData[i] < 16) Serial.print("0");
    Serial.print(spiData[i], HEX);
    Serial.print(" RX=0x");
    if (rx < 16) Serial.print("0");
    Serial.println(rx, HEX);
  }
  Serial.println();

  Serial.println("Setup complete. Reading RTC...");
  Serial.println();
}

В setup() мы инициализируем все три протокола. Затем мы:

  1. Сканируем шину I2C на наличие устройств с адресами от 1 до 126.
  2. Тестируем EEPROM, записывая значения 42 и 99 по адресам 0 и 1, затем читая их обратно и проверяя.
  3. Тестируем SPI, отправляя три байта (0xAA, 0x55, 0x42) и выводя полученные байты.

Loop

void loop() {
  // ── Serial: Print RTC time every 2 seconds ──
  byte hr, min, sec;
  readRTC(hr, min, sec);

  Serial.print("[RTC] ");
  if (hr < 10) Serial.print("0");
  Serial.print(hr);
  Serial.print(":");
  if (min < 10) Serial.print("0");
  Serial.print(min);
  Serial.print(":");
  if (sec < 10) Serial.print("0");
  Serial.print(sec);

  Serial.print("  |  Uptime: ");
  Serial.print(millis() / 1000);
  Serial.println("s");

  delay(2000);
}

Каждые две секунды цикл считывает время RTC и выводит его вместе с временем работы системы. Это демонстрирует непрерывный вывод через Serial, пока I2C и SPI простаивают.

Ключевые концепции

Частые ошибки и советы по отладке

Предлагаемые расширения

  1. Добавьте реальные датчики: Подключите RTC DS1307 и EEPROM 24Cxx к шине I2C, а модуль SD-карты через SPI.
  2. Записывайте данные в EEPROM: Сохраняйте показания датчиков и извлекайте их после сброса.
  3. Используйте прерывания: Запускайте передачу SPI по нажатию кнопки.
  4. Реализуйте командный интерфейс: Разбирайте команды Serial для управления операциями I2C и SPI.

Попробуйте сами

Готовы увидеть все три протокола в действии? Откройте живой пример в Velxio, скомпилируйте и запустите. Наблюдайте за выводом монитора порта, пока Arduino сканирует шину I2C, тестирует EEPROM, выполняет передачи SPI, а затем непрерывно выводит время RTC.

Запустить мультипротокольную демонстрацию на Velxio

Панель инструментов редактора Velxio с кнопками компиляции и запуска Панель инструментов холста Velxio над окном симуляции

Удачного программирования!


Поделиться:
David Montero

Автор

David Montero

Creator of Velxio, the open-source circuit and Arduino simulator.

GitHub velxio.dev

Похожие статьи


Предыдущая статья
Мигание светодиодом на Raspberry Pi Pico с симулятором Velxio
Следующая статья
SPI-тест с обратной связью: Освойте протокол с Velxio