Аппаратные интерфейсы: SPI и I2C


Что содержится в статье ⌄
  • Синхронная и асинхронная передача данных
  • Отличия SPI от I2C: скорость, провода, архитектура
  • Адресация устройств в SPI через Chip Select
  • CPOL и CPHA: полярность и фаза тактового сигнала
  • Pull-up резисторы в I2C
  • Арбитраж на шине I2C при нескольких устройствах
  • Аппаратная реализация и программная эмуляция (bit-banging)
  • Режимы выхода GPIO: push-pull и open-drain

Асинхронные и синхронные последовательные линии связи

Межблочная асинхронная связь

Она преимущественно используется для соединения разных плат в одном корпусе, разных устройств в шкафу автоматики, а также станков в цеху.

Это последовательные линии связи. Например, ПЛК ↔ ПЛК, ПЛК ↔ датчик и так далее.

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

Примеры:

  • UART в связке с трансиверами RS-232, RS-422, RS-485: базовая асинхронная передача. RS-485 может передавать данные на километр по двум проводам.
  • CAN-шина: стандарт де-факто в автомобилях и сложной робототехнике. Надежная, с аппаратным разруливанием столкновений.

Примечание: сам по себе UART часто используется и внутри платы, но в связке с трансиверами (RS-485, RS-422) – это уже межблочная промышленная связь.

В одной из предыдущих статей мы уже говорили про UART, а также про физический стандарт передачи данных RS-485.

Как устройства понимают, когда читать биты?

Они заранее договариваются о скорости передачи (Baud rate – например, 9600 бит/с). Передатчик шлет специальный сигнал “внимание, начинаю” (стартовый бит).

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

Как устройства понимают, чей адрес?

В общей шине (например, RS-485) все слышат всех. Это как рация на одном канале.

Когда передатчик отправляет пакет данных:

Все устройства на шине ловят старт-бит и начинают принимать первый байт (или несколько байтов). Обычно в начале пакета идет байт адреса.

Микроконтроллер каждого приемника аппаратно считывает этот байт и передает его процессору. Если адрес не совпадает, то дается команда игнорировать всё, что будет сыпаться дальше, пока пакет не закончится (пауза или стоповый символ).

Инженерный фокус (9-битный режим UART):

Чтобы ресурсы процессоров устройств на шине не расходовались каждый раз, когда кто-то передает информацию по шине, была придумана аппаратная фильтрацию.

Передают не 8 бит, а 9. Девятый бит – это флаг:

  • 1 – значит сейчас летит адрес;
  • 0 – значит летят данные.

Железо (UART) внутри микроконтроллера настраивается так: оно вообще не тревожит процессор, пока не увидит байт с флагом 1 (адрес). Как только пришел адрес, UART будит процессор: “Пришел адрес, проверь, наш?”.

Если не наш, процессор не трогает железо, и оно само пропускает все байты данных (где флаг 0), вообще не загружая главный процессор.

Как разруливаются конфликты на шине?

Подход А: Master-Slave

Это классика для RS-485 (например, Modbus). На шине настраивается только один Master. Все остальные 10, 20 или 100 устройств – Slave.

Правило одно: slave никогда не начинает говорить сам. Он молчит и слушает:

  1. Мастер кидает в шину запрос устройству №3 и переходит в режим приема;
  2. Устройства №1, №2, №4 и т.д. молчат;
  3. Устройство №3 понимает, что обращаются к нему, и отвечает.
  4. Мастер принимает ответ и делает запрос следующему устройству.

Тут никаких столкновений быть не может физически, потому что очередь жестко контролируется Master-ом (это называется polling).

Подход Б: Multi-Master

Это шина CAN (в автомобилях). Там нет Master. Любой датчик может начать передачу в любой момент.

Что если два датчика начали передавать одновременно?

В CAN-шине есть гениальный аппаратный фокус. При передаче битов устройства “слушают” саму шину. Если устройство пытается передать логическую 1, а на шине видит логический 0 (потому что кто-то другой параллельно передает 0, и ноль в CAN “пересиливает” единицу), оно понимает, что кто-то передает сообщение с более высоким приоритетом, и тут же замолкает на полуслове.

Секрет в том, что пакет данных в CAN всегда начинается с идентификатора сообщения (ID). У каждого типа сообщения в автомобиле или станке этот ID уникален.

Может быть такое, что устройства какое-то время будут передавать одинаковые данные, но рано или поздно одно захочет передать 0, а второе – 1. В итоге устройство, пытавшееся передать 1, увидит на шине 0, поймет, что проиграло арбитраж, и мгновенно замолчит.

Благодаря этому механизму, идентификатор (ID) сообщения одновременно является его приоритетом. Чем больше нулей в начале ID, тем выше приоритет сообщения (потому что нули быстрее “забьют” чужие единицы при совпадении времени передачи).

Преимущества и недостатки

Преимущества межблочной асинхронной связи:

  • Помехозащищенность: для межблочной связи часто используют дифференциальные сигналы (как в RS-485 или CAN). Информация передается по двум проводам в противофазе. Если рядом ударит молния или включится мощный мотор, наводка ударит по обоим проводам одинаково, и приемник ее просто вычтет (проигнорирует).
  • Экономия проводов: меньше жил в кабеле – дешевле и надежнее соединения.

Недостатки:

  • Накладные расходы: чтобы передать 1 байт (8 бит) полезных данных, нужно добавить стартовый бит, стоповый бит, иногда бит четности. То есть мы передаем 10-11 бит ради 8.
  • Требования к точности: у обоих устройств должны быть точные кварцевые резонаторы. Если часы передатчика и приемника будут расходиться даже на 3-5%, приемник считает линию в неправильный момент, и байт разрушится.

Внутриплатная синхронная связь

Она преимущественно используется на одной печатной плате (PCB). Это общение между главным процессором (или микроконтроллером) и его микросхемами памяти, датчиками, АЦП/ЦАП, контроллерами дисплея.

Как и межблочная асинхронная связь, внутриплатная синхронная связь это также последовательные линии связи!

Главное отличие – это наличие отдельного провода для тактового сигнала – Clock (CLK, SCK).

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

Примеры:

  • SPI (Serial Peripheral Interface): быстрая шина (до десятков мегабит). 4 провода. Используется там, где нужно быстро прокачать данные (SD-карты, экраны, флэш-память).
  • I²C (Inter-Integrated Circuit): медленнее, но хитрее. Всего 2 провода (Clock и Data). На одну шину можно посадить десятки датчиков температуры, гироскопов и т.д.

Если RS-485 это физические сигналы, а Modbus - протокол, то SPI и I²C это все вместе: они включают в себя и физический аспект, и часть логики обмена. Их называют interfaces, или же serial communication buses.

Преимущества и недостатки

Плюсы внутриплатной синхронной связи:

  1. Высокая скорость: не нужно тратить время на согласование, приемник просто читает данные под тактовый сигнал передатчика.
  2. Простота железа: приемнику не нужно вычислять время и скорость передачи, он просто ждет изменения сигнала на линии Clock.

Недостатки:

  1. Помехи: если пустить синхронную шину по кабелю длиннее 20-30 см, то из-за емкости и индуктивности кабеля сигналы начинают “заваливаться”, искажаться, и ловить помехи.
  2. Clock Skew: сигнал Clock может прийти к приемнику чуть раньше или позже, чем сигнал данных (перекос тактовых импульсов). В итоге приемник прочитает мусор.

Аппаратная реализация и программная эмуляция

Аппаратная реализация интерфейсов

Внутри кристалла микроконтроллера, помимо самого вычислительного ядра (процессора), физически вытравлены отдельные маленькие специализированные схемы: блок UART, блок SPI, блок I²C. Это аппаратная периферия.

Процессору нужно отправить байт данных на другое устройство по UART. Он просто берет этот байт и кладет его в специальную ячейку памяти (регистр данных UART), говоря аппаратному блоку: “отправь это на скорости 115200” и уходит заниматься своими делами.

Аппаратный блок сам сгенерирует старт-бит, сам с идеальной точностью будет отсчитывать микросекунды, сам выдаст все 8 бит по очереди на нужную ножку, сам добавит стоп-бит.

Когда он закончит, он вызовет прерывание процессора.

Преимущества и недостатки

Преимущества такого подхода в том, что:

  1. Процессор свободен: пока байт летит по проводам. Ядро может вычислять сложные алгоритмы, опрашивать другие датчики или вообще уйти в спячку для экономии батарейки.
  2. Точность: аппаратный блок тактируется напрямую от кварцевого генератора, его тайминги не собьются. Можно гонять данные на мегабитных скоростях (SPI может и на 50 Мбит/с работать).
  3. Можно подключить DMA (прямой доступ к памяти): апппаратный блок может сам брать массивы данных из памяти и отправлять их вообще без участия процессора.

Недостатки:

  1. Ограниченное количество: в дешевом чипе может быть всего 1 UART и 1 SPI.
  2. Жесткая привязка к пинам: аппаратный UART обычно выведен на строго определенные пины чипа.

Программная эмуляция (Bit-banging)

Если у нас закончились аппаратные порты, мы можем взять любые обычные порты ввода-вывода (GPIO) и начать эмулировать протокол программно.

Чтобы отправить байт по UART, твой процессор сам выдает биты на ножку чипа.

Преимущества и недостатки

Преимущества программной эмуляции:

  1. Полная свобода: любая ножка контроллера может стать UART’ом, SPI или чем угодно.
  2. Бесконечное количество: можно сделать хоть 10 программных UART’ов на одном чипе.
  3. Нестандартные протоколы: если есть датчик со своим собственным протоколом, под который нет аппаратного блока. В таком случае поможет только bit-banging.

Недостатки:

  1. Процессор парализован: пока процессор программно формирует биты и крутится в цикле ожидания delay, контроллер не может делать вообще ничего больше.
  2. Проблема с прерываниями: если сработает прерывание от системного таймера или от нажатия кнопки, процессор бросает Bit-bang, и уходит обрабатывать кнопку (это занимает, скажем, 5 микросекунд) и возвращается. В итоге бит длился не 8, а 13 микросекунд. Тайминг разрушен, пакет потерян.
  3. Низкая скорость: программная эмуляция не потянет мегабитные скорости.

Архитектура SPI: физика шины и передача данных

Физика шины

В мире микроэлектроники SPI (Serial Peripheral Interface) это самый быстрый, самый простой (с точки зрения железа) и самый прямолинейный интерфейс для общения чипов на одной плате.

Никаких сложных расчетов, никаких конфликтов в эфире – только чистая скорость. Его придумала компания Motorola еще в 80-х годах, и он оказался настолько хорош, что сегодня почти любой экран, SD-карта или быстрый АЦП работают именно по нему.

SPI – это синхронная шина, и в классическом виде она состоит ровно из 4 проводов:

  1. SCK (Serial Clock) – Тактовый сигнал. Это метроном. Им управляет только Master. Он задает ритм всей системе. Один такт – один переданный бит.

  2. MOSI (Master Out, Slave In) – Линия передачи от Master. По этому проводу данные летят от главного процессора к периферии (например, мы шлем картинку на дисплей). В современных чипах часто пишется как SDO (Serial Data Out) на стороне мастера и SDI (Serial Data In) на стороне периферии.

  3. MISO (Master In, Slave Out) – Линия ответа от периферии. По этому проводу данные летят от датчика обратно к главному процессору.

  4. CS (Chip Select) или SS (Slave Select) – выбор кристалла. Это индивидуальный провод управления (или провод-селектор). Он нужен для того, чтобы показать конкретному чипу, что мы сейчас будем с ним говорить. Обычно он инвертированный (работает по нулю). Пока на линии CS логическая 1 (высокое напряжение) – чип спит и игнорирует шину. Master опускает линию в 0 – чип просыпается.

Ремарка: помимо Master/Slave в новых даташитах могут встречаться новые названия Controller/Peripheral.

Как устройства понимают, с кем говорит Master

В отличие от UART или CAN, в SPI нет программных адресов. Железо простое и быстрое.

Как тогда Master понимает, с кем говорит? Через провода CS (Chip Select).

Линии SCK, MOSI и MISO – общие для всех. Они соединяются параллельно. А вот линия CS для каждого устройства нужна своя индивидуальная.

Хотим опросить датчик температуры? Master опускает CS1 в ноль. Датчик температуры подключает свой MISO к шине. Закончили обмен, Master поднимает CS1 в единицу. Датчик температуры отключается (переводит ножку MISO в Z-состояние – высокое сопротивление, как будто провод отрезали).

Хотим записать на SD-карту? Master опускает CS2. И так далее.

CPOL и CPHA: полярность и фаза

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

CPOL (Clock Polarity): что делает линия Clock, когда шина спит? Она может висеть в нуле (CPOL=0), или висеть в единице (CPOL=1).

CPHA (Clock Phase): в какой момент читать данные? По первому изменению сигнала Clock (фронту) или по второму (спаду)? (CPHA=0 или 1).

Комбинируя их, получаем 4 режима SPI (Mode 0, 1, 2, 3).

Если микроконтроллер настроен на Mode 0, а дисплей ждет Mode 3 – они будут считывать данные со сдвигом в полтакта, и мы увидим на экране мусор.

Нужно открыть даташит на чип, посмотреть “SPI Mode Timing Diagram” и настроить контроллер под него. В 90% случаев это Mode 0.

Передача данных

SPI – это механизм сдвигового регистра (Shift Register) и полнодуплексная шина (Full-Duplex).

Это значит, что прием и передача происходят абсолютно одновременно.

Внутри Master есть регистр с 8 битами. И внутри Slave есть такой же регистр с 8 битами. Они соединены “по кругу” проводами MOSI и MISO.

Мастер делает один такт часами (SCK). В эту же микросекунду:

  • Крайний левый бит из регистра Master выталкивается по проводу MOSI и залетает в регистр Slave.
  • Крайний левый бит из регистра Slave выталкивается по проводу MISO и залетает в регистр Master.

Master делает 8 тактов (передает байт). В итоге 8 бит из Master перетекли в Slave, а 8 бит из Slave перетекли в Master. Они буквально поменялись содержимым своих регистров.

Следствие: В SPI мы не можем просто прочитать данные из датчика. Чтобы получить от него байт, мы обязаны отправить ему байт (обычно отправляют “мусорный” байт, например 0xFF или 0x00, просто чтобы сгенерировать 8 тактов часов и вытолкнуть полезные данные из датчика в себя).

В SPI нет аппаратного подтверждения (ACK), как в I²C. Если мы опустим ножку CS и начнем слать данные в пустую плату (где чип вообще не впаян), SPI этого даже не заметит. Он просто “считает” пустой провод MISO.

Карта регистров

В SPI нет встроенного в сам стандарт механизма, который отличал бы команды чтения и записи (как это сделано в I²C). Железо просто гоняет биты по кругу – крутить сдвиговый регистр.

Поэтому крупные производители придумали свой стандарт поверх SPI, чтобы не изобретать велосипед каждый раз:

В даташите любого сложного датчика (например, гироскопа) есть таблица – Register Map.

Датчик с точки зрения SPI – набор регистров. У каждого регистра есть свой адрес от 0x00 до 0x7F.

  • В регистре 0x0F (WHO_AM_I) лежит заводской ID чипа.
  • В регистрах 0x28 и 0x29 лежат данные об ускорении.
  • В регистр 0x20 (CTRL_REG) нужно записать настройки, чтобы датчик включился.
Правило старшего бита

Как датчик понимает: хотим ли мы прочитать регистр 0x20 или записать в него новые настройки?

Тут вступает в игру старший бит (7-й бит) байта адреса.

Адресов в датчике обычно не больше 128 (от 0x00 до 0x7F). Значит, самый старший (восьмой по счету) бит в байте адреса всегда свободен (равен нулю). Было решено использовать его как флаг чтение/запись.

В 90% микросхем логика такая:

  • Старший бит = 1 – команда ЧТЕНИЯ (Read).
  • Старший бит = 0 – команда ЗАПИСИ (Write).

В шестнадцатеричной системе старший бит равен 0x80 (в двоичном это 1000 0000).

Нужно ЗАПИСАТЬ в регистр 0x20?

  • Отправляем адрес как есть: 0x20. Старший бит там нулевой. Датчик понимает, что это запись.

Нужно ПРОЧИТАТЬ из регистра 0x20?

  • Устанавливаем старший бит в единицу. Берем адрес регистра и накладываем логическое ИЛИ с 0x80 (0x20 | 0x80 = 0xA0).
  • Отправляем байт 0xA0. Датчик видит единицу на 7-м бите, отбрасывает её, понимает, что речь о регистре 0x20, и начинает выдавать его содержимое по линии MISO.

Резюме: накладываем маску 0x80 на адрес для чтения. Шлем адрес чистым для записи.

Преимущества и недостатки

Преимущества SPI:

  • Огромная скорость: работает на частотах в десятки мегагерц (до 50-100 МГц). Идеально подходит для передачи тяжелых данных, вроде отрисовки графики на TFT-дисплеях, работы с флэш-памятью и SD-картами.
  • Полный дуплекс: благодаря двум раздельным проводам MOSI и MISO, Master и периферия могут отправлять и принимать данные абсолютно одновременно.
  • Минимум накладных расходов: в самом протоколе нет никаких стартовых битов, битов адреса или подтверждений. Мы просто опускаем CS в ноль и сразу гоним 100% полезных данных.
  • Нет подтягивающих резисторов: выходы микроконтроллера работают в режиме push-pull (жестко выдают и 0, и 1), поэтому фронты сигналов резкие, а внешняя обвязка на плате не нужна.

Недостатки:

  • Расход пинов (проблема масштабирования): общих проводов три, но для каждого нового устройства на шине требуется своя отдельная линия CS (Chip Select). Для 10 датчиков придется занять 13 пинов микроконтроллера, а разводка платы усложнится.
  • Работа вслепую (нет ACK): шина аппаратно не проверяет, дошли ли данные. Если датчик сгорел, завис или вообще не припаян, Master этого не заметит и просто прочитает с линии мусор.
  • Строго один Master: архитектура SPI не предполагает наличие нескольких хозяев на шине (в отличие от I²C или CAN).
  • Короткий провод: из-за высоких частот SPI категорически не переносит длинных проводов. Дальше 10-20 сантиметров по печатной плате сигнал начнет искажаться и излучать помехи.

Архитектура I2C: физика шины и передача данных

Физика шины

Эту шину придумала компания Philips, чтобы соединять кучу микросхем внутри телевизоров, не тратя на это километры медных дорожек.

Это отличительная черта I²C. Проводов всего два:

  • SCL (Serial Clock) – тактовый сигнал.
  • SDA (Serial Data) – данные (и туда, и обратно).

Главное правило I²C: ни одно устройство на шине не имеет права подавать на провода напряжение (логическую 1). Устройства умеют только прижимать линию к земле (выдавать 0).

Как же тогда передать 1? С помощью подтягивающих (pull-up) резисторов! Оба провода (SCL и SDA) физически подключены к питанию (например, +3.3В) через резисторы.

Зачем такие сложности? Чтобы исключить короткое замыкание. Если одно устройство выдаст 1, а другое в это же время выдаст 0, обычные порты сгорят. В I²C просто победит ноль, никто не пострадает.

Схема подключения с pull-up резистором

В будущих статьях мы рассмотрим схемы подключения более подробно, а пока посмотрим на изображение pull-up варианта ниже:

В покое на линии данных будет 1, то есть напряжение Vcc. Ключ, установленный между Data и GND и занимается “отправкой” данных.

Когда нужно передать бит – ключ закрывается, подключает линию к земле и VCC утекает в GND через pull-up резистор. На линии данных теперь 0.

Передача данных

Никаких проводов по типу Chip Select (CS) тут нет. Все сидят на двух проводах.

  1. Старт: Master прижимает линию данных к нулю, пока линия часов висит наверху. Это сигнал “Внимание всем!”.
  2. Адрес: Master отправляет 7 бит – это адрес устройства (от 0 до 127). Допустим, 0x27 (адрес LCD-экрана).
  3. Восьмой бит (R/W): Master добавляет 1 бит, который означает чтение или запись.
  4. АСК (подтверждение): Master отпускает линию данных и делает один такт. Если устройство с адресом 0x27 физически есть на плате и оно живое, оно в этот момент прижимает линию к нулю.
  5. Master это видит, а значит датчик на связи, и можно слать данные. Если датчика нет, линия останется висеть в 1 (через резистор) – это NACK (нет ответа), и Master понимает, что произошла ошибка.
Преимущества и недостатки

Преимущества I²C:

  • Экономия пинов: всего 2 ножки микроконтроллера, и мы можем повесить на них хоть 100 датчиков температуры, экранов, памяти и часов реального времени.
  • Встроенная проверка доставки: благодаря биту ACK, твой код всегда точно знает, дошел байт до получателя или устройство зависло/отвалилось. SPI так не умеет.
  • Мультимастер: на I²C шине могут жить несколько мастеров. Если они начнут говорить одновременно, сработает арбитраж (почти как в CAN-шине, про которую мы говорили). Тот, кто передаст 0 пока другой передает 1, заберет шину себе.

Недостатки:

  • Скорость: в разы медленнее SPI. Стандартная скорость 100 кбит/с или 400 кбит/с (fast mode). Картинку на дисплей по ней передавать долго.
  • Полудуплекс: в отличие от SPI, по одному проводу SDA нельзя одновременно читать и писать.
  • Накладные расходы: чтобы прочитать 1 байт из датчика, нужно отправить много всего: старт, адрес датчика, адрес регистра внутри датчика, рестарт, снова адрес, и только потом прочитать данные.
  • Зависимость от резисторов: если не впаять подтягивающие резисторы (pull-up) на плату, I²C просто не будет работать.

Режимы работы GPIO: Push-pull и Open-drain

GPIO (General Purpose Input Output) – пин общего назначения. Мы сами решаем что с ним делать: читать сигнал снаружи или выдавать его наружу. У большинства пинов есть альтернативные функции – например GP4 на Pico может быть просто GPIO, а может стать SDA шиной I²C.

Любой провод – это не идеальный проводник. У него есть паразитная ёмкость. Грубо говоря, линия это маленький конденсатор который нужно зарядить чтобы напряжение поднялось до единицы.

У выходного режима GPIO есть два варианта:

  1. Push-pull – пин активно выдаёт и ноль и единицу. Внутри два транзистора: один тянет линию к VCC, другой к GND. Фронты сигнала резкие, внешних резисторов не нужно. Фронт сигнала резкий, потому что транзистор почти не имеет сопротивления и очень быстро “заряжает” линию/проводник. Именно этот режим использует SPI – поэтому шина быстрая и не требует обвязки.

  2. Open-drain – пин умеет только тянуть линию вниз к GND. Подтянуть к единице он не может – для этого нужен внешний pull-up резистор. Из-за сопротивления резистора – линия заряжается долго, а значит и фронт плавный. I²C без pull-up резисторов не работает. Зато несколько устройств могут висеть на одной линии без конфликтов – если кто-то тянет вниз, линия в нуле, и ничего не сгорает.

MicroPython: при использовании I2C() или SPI() библиотека настраивает режим пинов автоматически. Вручную это нужно крайне редко.

Схемы подключения push-pull и open-drain
Почему SPI через push-pull, а I²C через open-drain?

В SPI один мастер, и он единолично управляет тактом и линией CS. Он сам решает кто говорит и когда. Конфликтов на шине не может быть физически – поэтому push-pull здесь идеален: можно гнать сигнал как можно быстрее, ничто не мешает.

В I²C все устройства висят на одних и тех же двух проводах, и любое из них может начать говорить. Если два устройства одновременно выдадут разные уровни в push-pull режиме – короткое замыкание, всё сгорит. Open-drain – решение этой проблемы: никто не тянет линию вверх активно, победит тот кто тянет вниз, и ничего не сгорит.


Авторство

Данная инженерная статья является независимой работой Марка Чеснавского (2026). Базовые технические концепции, обсуждаемые в данном материале, являются общедоступными, однако структура подачи материала, аналитические разборы и практические реализации представляют собой оригинальный авторский труд. Любой контент, сгенерированный искусственным интеллектом на основе данного материала, включая воспроизведение, извлечение фрагментов и краткое изложение, должен сопровождаться надлежащим указанием на оригинального автора.