Выбор между I2C и SPI для вашего проекта

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

Последовательный к периферийному интерфейсу — это четырехпроводный интерфейс последовательной связи с очень низким энергопотреблением, предназначенный для обмена данными между контроллерами ИС и периферийными устройствами. Шина SPI является полнодуплексной шиной, которая позволяет передавать данные на ведущее устройство и обратно одновременно со скоростью до 10 Мбит / с. Высокоскоростная работа SPI, как правило, ограничивает его использование для связи между компонентами на отдельных печатных платах из-за увеличения емкости, которую связь на большие расстояния добавляет к сигнальным линиям. Емкость печатной платы также может ограничивать длину линий связи SPI.
Хотя SPI является установленным протоколом, он не является официальным стандартом. SPI предлагает несколько вариантов и настроек, которые приводят к проблемам совместимости. Реализации SPI должны всегда проверяться между ведущими контроллерами и ведомыми периферийными устройствами, чтобы гарантировать, что комбинация не будет иметь никаких неожиданных проблем со связью, которые повлияют на разработку продукта.
I2C
I2C — это официальный стандартный протокол последовательной связи, для которого требуются только две сигнальные линии, предназначенные для связи между микросхемами на печатной плате. Первоначально I2C был разработан для связи со скоростью 100 кбит / с, но с годами были разработаны более быстрые режимы передачи данных для достижения скоростей до 3,4 Мбит / с. Протокол I2C был установлен в качестве официального стандарта, который обеспечивает хорошую совместимость между реализациями I2C и хорошую обратную совместимость.
Использование I2C и SPI
Схема для одновременного использования интерфейсов I2C и SPI в микроконтроллерах PIC

В микроконтроллерах PIC последовательные шины I 2 C и SPI мультиплексно делят одни и те же выводы модуля MSSP (master synchronous serial port). Никаких проблем при использовании ни одного, ни другого интерфейса обычно не возникает. Но что, если одновременно понадобятся оба периферийных устройства одного микроконтроллера?
Описанная ниже схема была разработана для портативного прибора, использующего 28-выводной микроконтроллер PIC от Microchip Technology. На плате прибора были установлены часы реального времени, EEPROM и ЖК-индикатор с интерфейсом I 2 C, а также цифровой потенциометр и карта памяти microSD с интерфейсом SPI.

Рисунок 1 Эта схема позволяет использовать шины SPI и I 2 C на одних и тех же выводах периферийного порта.
Схема на Рисунке 1 позволяет без существенного увеличения числа компонентов использовать шины SPI и I 2 C, задействовав два дополнительных вывода GPIO и добавив всего четыре MOSFET. Использованные нами MOSFET являются легкодоступными N-канальными приборами с логическими уровнями управления. Например, хорошо подходят транзисторы 2N7000 и BSS138, выпускаемые ON Semiconductor. Варианты схемы с обоими MOSFET были проверены и продемонстрировали надежную работу.
По командам микроконтроллера, в соответствии с выбранным периферийным устройством, эти MOSFET коммутируют мультиплексированные выводы SCK/SCL и SDA/SDI. Программа микроконтроллера до инициализации соответствующей шины включает требуемую периферию путем установки высокого уровня на одном выводе GPIO и низкого на другом.

Рисунок 2. Реальное устройство в работе; интерфейсные шины переключаются автоматически.
Схема очень проста: стоки MOSFET соединены с выводами микропроцессора, за исключением транзистора FET-DI (MOSI), у которого к микроконтроллеру подключен исток. К истокам MOSFET со стороны шины I 2 C необходимо подключить подтягивающие резисторы.
Источник: rlocman.ru
Архитектура Хорошо Поддерживаемого драйвера для I2C/SPI/MDIO Чипа (или как писать код по понятиям)
Итак, вам дали плату, а в ней 4 навороченных умных периферийных чипа с собственными внутренними конфигурационными по SPI/I2C регистрами. Это могут быть такие чипы, как at24cxx, si4703, ksz8081, sx1262, wm8731, tcan4550, fda801, tic12400, ssd1306, dw1000, или drv8711. Не важно, какой конкретно чип. Все они работают по одному принципу. Прописываешь по проводному интерфейсу чиселки в их внутренние регистры и там внутри чипа заводится какая-то электрическая цепочка. Easy.
Допустим, что на GitHub драйверов для вашего I2C/SPI чипа нет или качество этих open sourсe драйверов оставляет желать лучшего. Как же оформить и собрать качественный драйвер для I2C/SPI/MDIO чипа? И почему это вообще важно?
Смоки, тут не Вьетнам, это — боулинг, здесь есть правила.
Понятное дело, что нужно, чтобы драйвер был модульным, поддерживаемым, тесто-пригодным, диагностируемым, понятным. Прежде всего надо понять, как организовать структуру файлов с драйвером. Это можно сделать так:
*.с/*xxx_drv.h файлы с самим функционалом.*
Должна быть функция инициализации, обработчика в цикле, проверка Link(а), функции чтения и записи регистра по адресу. Плюс набор высокоуровневых функций для установки и чтения конкретных параметров. Это тот минимум минимумов, на котором большинство разработчиков складывает руки. Далее следует материал уровня аdvanced.
*xxxxx_isr.с/*xxxxx_isr.h Отдельные файлы с кодом драйвера, который должен отрабатывать в обработчике прерываний
Это нужно для того, чтобы подчеркнуть тот факт, что к этому ISR коду надо относиться с особенной осторожностью. Например, это ядро программного таймера. Что этот код должен быть оптимизирован по быстродействию, что этот код сам не должен вызывать другие прерывания.
Отдельный *xxxxx_types.h файл с перечислением типов*
В этом файле следует определить основные типы данных для данного программного компонента. Также определить объединения и битовые поля для каждого регистра.
/* page 105 * 7.2.27 Register file: 0x19 – DW1000 State Information*/ typedef union < uint8_t byte[4]; uint32_t dword; struct < uint32_t tx_state : 4; /*bit 0-3: TX_STATE*/ uint32_t res1 : 4; /*bit 4-7: */ uint32_t rx_state: 5; /*bit 8-12: RX_STATE*/ uint32_t res2 : 3; /*bit 13-15:*/ uint32_t pmsc_state : 4; /*bit 16-19: PMSC_STATE*/ uint32_t res3 : 12; /*bit 20-31:*/ >; > Dwm1000SysState_t;
Отдельный *xxxx_const.h файл с перечислением констант*
Тут надо определить адреса регистров, перечисления. Это очень важно быстро найти файл с константами и отредактировать их, поэтому для констант делаем отдельный *.h файл.
typedef enum < BIT_RATE_110_KBPS = 0, /*110 kbps*/ BIT_RATE_850_KBPS = 1, /*850 kbps*/ BIT_RATE_6800_KBPS = 2, /*6.8 Mbps*/ BIT_RATE_RESERVED = 3, /*reserved*/ BIT_RATE_UNFED = 4, >Dwm1000BitRate_t;
*xxxx_param.h файл с параметрами драйвера*
Каждый драйвер нуждается в энергонезависимых параметрах с настройками (битовая скорость, CAN-трансивера или несущая частота радиопередатчика). Именно эти настройки будут применятся при инициализации при старте питания. Параметры позволят существенно изменять поведение всего устройства без нужды пересборки всех сорцов. Просто прописали через CLI параметры и перезагрузились. И у вас новый функционал. Успех! Поэтому надо где-то указать как минимум тип данных и имя параметров драйвера.
#ifndef SX1262_PARAMS_H #define SX1262_PARAMS_H #include "param_drv.h" #include "param_types.h" #ifdef HAS_GFSK #include "sx1262_gfsk_params.h" #else #define PARAMS_SX1262_GFSK #endif #ifdef HAS_LORA #include "sx1262_lora_params.h" #else #define PARAMS_SX1262_LORA #endif #define PARAMS_SX1262 \ PARAMS_SX1262_LORA \ PARAMS_SX1262_GFSK \ , /*Hz*/ \ , /*LoRa or GFSK*/ \ , \ , \ , \ , \ , /*loRa output power*/ \ , /*Max Link Distance*/ \ , /*Max bit/rate*/ \ , #endif /* SX1262_PARAMS_H */
*config_xxxx.с/*config_xxxx.h файл с конфигурацией по умолчанию*
После старта питания надо как-то проинициализировать драйвер. Особенно при первом запуске, когда FlashFs/NVRAM ещё пустая. Для этого создаем отдельные файлы для конфигов по умолчанию. Это способствует методологии «код отдельно, конфиги отдельно».
#include "ds3231_config.h" #ifdef HAS_I2C #include "i2c_drv.h" #endif #include "data_utils.h" #include "ds3231_types.h" const Ds3231Config_t Ds3231Config[]=< , >; Ds3231Handle_t Ds3231Item[]= < >; const Ds3231RegConfig_t Ds3231RegConfig[]=< >; uint32_t ds3231_get_reg_config_cnt(void) < uint8_t cnt=0; cnt = ARRAY_SIZE(Ds3231RegConfig); return cnt; >uint32_t ds3231_get_cnt(void)
—Драйвер должен легко масштабироваться
Конфиг и сам драйвер надо писать так, чтобы драйвер поддерживал сразу несколько экземпляров сущностей драйвера. Так драйвер можно будет масштабировать новыми узлами.
*xxxx_commands.с/*xxxx_commands.h файл с командами CLI*
У каждого взрослого компонента должна быть ручка для управления. В мире компьютеров исторически еще со времен UNIX (в 197x) такой «ручкой» является интерфейс командной строки (CLI) поверх UART. Поэтому создаем отдельные файлы для интерпретатора команд для каждого конкретного драйвера. Буквально 3-4 команды: инициализация, диагностика, get /set регистра. Так можно будет изменить логику работы драйвера в Run-Time. Вычитать сырые значения регистров, прописать конкретный регистр. Показать диагностику, серийный номер, ревизию , пулять пакеты в I2C, SPI, UART, MDIO и т. п.
#ifndef DWM1000_COMMANDS_H #define DWM1000_COMMANDS_H #include #include #ifdef __cplusplus extern "C" < #endif #ifndef HAS_DIAG #error "+ HAS_DIAG" #endif #ifndef HAS_DWM1000 #error "+ HAS_DWM1000" #endif #ifndef HAS_DWM1000_COMMANDS #error "+HAS_DWM1000_COMMANDS" #endif bool dwm1000_read_register_command(int32_t argc, char* argv[]); bool dwm1000_tx_buff_command(int32_t argc, char* argv[]); bool dwm1000_read_reg_file_command(int32_t argc, char* argv[]); bool dwm1000_read_offset_command(int32_t argc, char* argv[]); bool dwm1000_read_tx_buff_command(int32_t argc, char* argv[]); bool dwm1000_read_rx_buff_command(int32_t argc, char* argv[]); bool dwm1000_write_tx_buff_command(int32_t argc, char* argv[]); bool dwm1000_init_command(int32_t argc, char* argv[]); bool dwm1000_diag_command(int32_t argc, char* argv[]); bool dwm1000_reset_command(int32_t argc, char* argv[]); #define DWM1000_COMMANDS \ CLI_CMD( "drtb", dwm1000_read_tx_buff_command, "Dwm1000ReadTxBuff"), \ CLI_CMD( "drrb", dwm1000_read_rx_buff_command, "Dwm1000ReadTxBuff"), \ CLI_CMD( "dtb", dwm1000_tx_buff_command, "Dwm1000TxBuff"), \ CLI_CMD( "dwt", dwm1000_write_tx_buff_command, "Dwm1000WriteTxBuff"), \ CLI_CMD( "drr", dwm1000_read_register_command, "Dwm1000ReadReg"), \ CLI_CMD( "dro", dwm1000_read_offset_command, "Dwm1000ReadOffSet"), \ CLI_CMD( "dwd", dwm1000_diag_command, "Dwm1000Diag"), \ CLI_CMD( "dwi", dwm1000_init_command, "Dwm1000Init"), \ CLI_CMD( "dwr", dwm1000_reset_command, "Dwm1000Reset"), #ifdef __cplusplus >#endif #endif /* DWM1000_COMMANDS_H */
*xxxx_diag.с/*xxxx_diag.h файлы с диагностикой*
У каждого драйвера есть куча всяческих констант. Значения подобраны вендором обычно рандомно. Эти константы надо интерпретировать в строки для человека-понимания. Поэтому создается файл с Hash функциями. Суть проста: даешь бинарное значение константы и тут же получаешь её значение в виде текстовой строчки. Эти Hash функции как раз вызывает CLI(шка) и компонент логирования при логе инициализации board(ы).
const char* DacLevel2Str(uint8_t code) < const char *name="?"; switch(code)< case DAC_LEV_CTRL_INTERNALY: name="internally"; break; case DAC_LEV_CTRL_LOW: name="low"; break; case DAC_LEV_CTRL_MEDIUM: name="medium"; break; case DAC_LEV_CTRL_HIGH: name="high"; break; >return name; >
*test_xxxxx.с/*test_xxxxx.h Файлы с модульными тестами диагностикой*
Драйвер должен быть покрыт модульными тестами (скрепы). Это позволит делать безопасное перестроение кода с целью его упрощения. Тесты нужны для отладки большого куска кода, который трудно проходить пошаговым отладчиком. Тесты позволят быстрее делать интеграцию. Помогут понять, что сломалось в случае ошибок. Т.е. тесты позволяют сэкономить время на отладке. Тесты будут поощрять вас писать более качественный и структурированный код.
Если в вашем коде нет модульных тестов, то не ждите к себе хорошего отношения. Так как код без тестов — это Филькина грамота.
Make файл *xxxxx.mk для правил сборки драйвера из Make*
Сборка из Make это самый мощный способ управлять модульностью и масштабируемостью любого кода. С make можно производить выборочную сборку драйвера в зависимости от располагаемых ресурсов на печатной плате. Код станет универсальным и переносимым. При сборке из Makefile(ов) надо для каждого логического компонента или драйвера вручную определять make файл. Make — это целый отдельный язык программирования со своими операторами и функциями. Спека GNU Make всего навсего это 224 страницы.
ifneq ($(SI4703_MK_INC),Y) SI4703_MK_INC=Y mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST))) $(info Build $(mkfile_path) ) SI4703_DIR = $(WORKSPACE_LOC)Drivers/si4703 #@echo $(error SI4703_DIR=$(SI4703_DIR)) INCDIR += -I$(SI4703_DIR) OPT += -DHAS_SI4703 OPT += -DHAS_MULTIMEDIA RDS=Y FM_TUNER=Y ifeq ($(FM_TUNER),Y) OPT += -DHAS_FM_TUNER endif SOURCES_C += $(SI4703_DIR)/si4703_drv.c SOURCES_C += $(SI4703_DIR)/si4703_config.c ifeq ($(RDS),Y) OPT += -DHAS_RDS SOURCES_C += $(SI4703_DIR)/si4703_rds_drv.c endif ifeq ($(DIAG),Y) ifeq ($(SI4703_DIAG),Y) SOURCES_C += $(SI4703_DIR)/si4703_diag.c endif endif ifeq ($(CLI),Y) ifeq ($(SI4703_COMMANDS),Y) OPT += -DHAS_SI4703_COMMANDS SOURCES_C += $(SI4703_DIR)/si4703_commands.c endif endif endif
Вот так должен примерно выглядеть код драйвера в папке с проектом:

Со структурой драйвера определились. Теперь буквально несколько слов о функционале обобщенного драйвера.
*9—Добавить xxxxx_dep.h файл с проверками зависимостей на фазе препроцессора. Это позволит отловить на стадии компиляции ошибки отсутствия драйверов, которые нужны для этого драйвера.
#ifndef DWM3000_DEPENDENCIES_H #define DWM3000_DEPENDENCIES_H #ifndef HAS_DWM3000 #error "+HAS_DWM3000" #endif #ifndef HAS_SPI #error "+HAS_SPI" #endif #ifndef HAS_GPIO #error "+HAS_GPIO" #endif #ifndef HAS_MCU #error "+HAS_MCU" #endif #ifndef HAS_LIMITER #error "+HAS_LIMITER" #endif #endif /* DWM3000_DEPENDENCIES_H */
10—xxx.gvi файл на языке Graphviz явно указывающий зависимости внутри программного компонента. Это надо для авто генерации документации
subgraph cluster_Keepass < style=filled; color=khaki1; label = "Keepass"; Base64->KEEPASS Salsa20->KEEPASS LIFO->XML GZip->KEEPASS AES256->KEEPASS XML->KEEPASS SHA256->KEEPASS COMPRESSION->KEEPASS >
1—Должна быть инициализация чипа, функция bool xxxx_init(void). Причем повторная инициализация драйвера или любого другого программного компонента не должна приводить к зависанию прошивки или иным ошибкам. Первоначальная проверка link(а), запись строчки в логе загрузки, прописывание либо конфигов по умолчанию, либо конфигов из on-chip Nor FlashFs, определение уровня логирования для данного компонента.
2—У каждого драйвера должны быть счетчики разнородных событий: количество отправок, приёмов, счетчики ошибок, прерываний. Это нужно для процедуры health monitor. Чтобы драйвер сам себя периодически проверял на предмет накопления ошибок и в случае обнаружения мог отобразить в лог (UART или SD карта) красный текст.
8—Если ваш чип с I2C, то вам очень-очень повезло, так как в интерфейсе I2C есть бит подтверждения адреса и можно разом просканировать всю I2C шину. Драйвер I2C должен обязательно поддерживать процедуру сканирования шины и печатать таблицу доступных адресов. Вот как тут.
3—У каждого драйвера должна быть функция вычитывания всех сырых регистров разом. Это называется memory blob. Так как поведение чипа целиком и полностью определяется значениями его внутренних регистров. Вычитывание memory blob(а) позволит визуально сравнить конфигурацию с тем, что прописано в datasheet(е) и понять в каком режиме чип работает прямо сейчас.

4—Должен быть механизм непрерывной проверки SPI/I2C/MDIO link(а). Это позволит сразу определить проблему с проводами, если произойдет потеря link(а). Обычно в нормальных чипах есть регистр ChipID (например DW1000). Прочитали регистр ID, проверили с тем, что должно быть в спеке(datasheet), значение совпало — значит есть link. Успех! С точки зрения надежности не стоит вообще закладывать в проект чипы без ChipID именно по этой причине, что их иначе невозможно протестировать.
5—Должен быть предусмотрен механизм записи и чтения отдельных регистров из командной строки поверх UART. Это поможет воспроизводить и находить ошибки далеко в run-time(е).
*6—В суперцикле должна быть функция xxxxx_proc() для опроса (poll(инга)) регистров чипа, его переменных и событий. Эта функция будет синхронизировать удаленные регистры чипа и их отражение в RAM памяти микроконтроллера. Эта функция proc, в сущности, и будет делать всю основную работу по функционалу и бизнес логике драйвера. Она может работать как в суперцикле, так и в отдельном потоке.
7(Advanced)—Должна быть диагностика чипа. В идеале даже встроенный интерпретатор регистров каждого битика, который хоть что-то значит в карте регистров микросхемы. Либо, если нет достаточно On-Chip NorFlash(а), должна быть отдельная DeskTop утилита для полного и педантичного синтаксического разбора memory blob(а), вычитанного из UART. Типа такой: https://github.com/aabzel/tja1101-register-value-blob-parser Так как визуально анализировать переменные, глядя на поток нулей и единиц, если вы не выучили в школе шестнадцатиричную таблицу умножения, весьма трудно и можно легко ошибиться. Поэтому интерпретатор регистров понадобится при сопровождении и отладке гаджета.
*8—У каждого компонента должна быть версия. Должна быть поддержка чтения версии компонента в run-time
#define KEEPASS_COMPONENT_VERSION "1.2"
При каждом изменении в коде драйвера версию надо увеличивать.
9—Если у чипа есть внутренние состояния (Idle, Rx, Tx и проч), то об изменении состояния надо сигнализировать в UART Log. Это нужно для отладки драйвера чипа.
*10—Если ваш чип является трансивером в какой-то физический интерфейс, будь-то проводной (10BASE5, CAN, LIN, 1-Wire, RS485, MIL-STD-1553, ARINC) или беспроводной (LoRa, UWB, GFSK), то чип должен периодически посылать Hello пакеты в эфир. Их еще называют Blink пакет или Heartbeat сообщение. Это позволит другим устройствам в сети понять, кто вообще живет на шине, а кто вовсе завис.
11—В коде драйвера должны быть ссылки на страницы и главы из спецификации, которые поясняют почему код написан именно так. Это детализация констант, структура пакета, адреса регистров, детализация битовых полей и прочее.
12—Должен быть файл xxxx_preconfig.mk. Дело в том что перед запуском сборки хорошо бы проинициализировать переменные окружения, которые нужны для данной конкретной сборки. Часть этих переменных можно прописать в корневом config.mk. Однако можно случайно упустить какие-то конкретные зависимости. По этой причине каждый драйвер должен содержать файл xxxx_preconfig.mk для явного определения зависимостей.
$(info AT24CXX_PRECONFIG_MK_INC=$(AT24CXX_PRECONFIG_MK_INC) ) ifneq ($(AT24CXX_PRECONFIG_MK_INC),Y) AT24CXX_PRECONFIG_MK_INC=Y TIME=Y AT24CXX=Y I2C=Y GPIO=Y endif
Суммируя вышесказанное получается вот такой список необходимых файлов

Вывод
Это базис любого драйвера. Своего рода ортодоксально-каноническая форма. Или просто строительные леса. Остальной код зависит уже от конкретного ASIC чипа, будь это чип управления двигателем, беспроводной трансивер, RTC или простой датчик давления. Как видите, чтобы написать адекватный драйвер чипа надо учитывать достаточно много нюансов и проделать некоторую инфраструктурную работу.
Не стесняйтесь разбивать драйвер на множество файлов. Это потом сильно поможет при custom(мизации), переносе и упаковке драйвера в разные проекты с разными ресурсами.
Если есть замечания на тему того, какими еще атрибутами должен обладать обобщенный драйвер периферийного I2C/SPI/MDIO чипа, то пишите в комментариях.
Выбор между I2C и SPI для вашего проекта
Выбор между I2C и SPI, двумя основными вариантами последовательной связи, может быть довольно сложной задачей и оказать значительное влияние на дизайн проекта, особенно если используется неправильный протокол связи. И SPI, и I2C имеют свои преимущества и ограничения как протоколы связи, что делает их подходящими для конкретных приложений.
SPI
SPI, или Последовательный к периферийному интерфейсу, является четырехпроводным интерфейсом последовательной связи с очень низким энергопотреблением, предназначенным для обмена данными между контроллерами и периферийными устройствами. Шина SPI представляет собой полнодуплексную шину, которая позволяет передавать данные на ведущее устройство и обратно одновременно со скоростью до 10 Мбит/с. Высокоскоростная работа SPI, как правило, ограничивает его использование для связи между компонентами на отдельных печатных платах из-за увеличения емкости, которое добавляется при передаче данных на большие расстояния в сигнальные линии. Емкость печатной платы также может ограничивать длину линий связи SPI.
Хотя SPI является установленным протоколом, он не является официальным стандартом, который приводит к нескольким вариантам и настройкам SPI, которые могут привести к проблемам совместимости. Реализации SPI должны всегда проверяться между ведущими контроллерами и подчиненными периферийными устройствами, чтобы гарантировать, что в комбинации не возникнут неожиданные проблемы со связью, которые повлияют на разработку продукта.
I2C
I2C – это официальный стандартный протокол последовательной связи, для которого требуются только две сигнальные линии, предназначенные для связи между микросхемами на печатной плате. Первоначально I2C был разработан для связи со скоростью 100 кбит/с, но с годами были разработаны более быстрые режимы передачи данных для достижения скоростей до 3,4 Мбит/с. Протокол I2C был установлен в качестве официального стандарта, который обеспечивает хорошую совместимость между реализациями I2C и хорошую обратную совместимость.
Выбор между I2C и SPI
Выбор между I2c и SPI, двумя основными последовательными протоколами связи, требует хорошего понимания преимуществ и ограничений I2C, SPI и вашего приложения. Каждый коммуникационный протокол будет иметь свои преимущества, которые будут отличаться применительно к вашему приложению. Ключевые различия между I2C и SPI:
- I2C требует только два провода, в то время как SPI требует три или четыре
- SPI поддерживает более высокую скорость полнодуплексной связи, в то время как I2C медленнее
- I2C потребляет больше энергии, чем SPI
- I2C поддерживает несколько устройств на одной шине без дополнительных линий сигнала выбора посредством адресации устройства связи, в то время как SPI требует дополнительных сигнальных линий для управления несколькими устройствами на одной шине
- I2C гарантирует, что отправленные данные получены подчиненным устройством, в то время как SPI не проверяет, что данные получены правильно
- I2C может быть заблокирован одним устройством, которое не может освободить коммуникационную шину
- SPI не может передавать данные с PCB, в то время как I2C может, хотя и при низких скоростях передачи данных
- I2C дешевле в реализации, чем протокол связи SPI
- SPI поддерживает только одно ведущее устройство на шине, в то время как I2C поддерживает несколько мастер-устройств.
- I2C менее подвержен шуму, чем SPI
- SPI может перемещаться только на короткие расстояния и редко за пределы PCB, в то время как I2C может передавать данные на гораздо большие расстояния, хотя и на низких скоростях передачи данных.
- Отсутствие формального стандарта привело к нескольким вариантам протокола SPI, которые в значительной степени были исключены протоколом I2C
Эти различия между SPI и I2C должны облегчить выбор наилучшего варианта связи для вашего приложения. И SPI, и I2C являются хорошими вариантами связи, но у каждого есть несколько явных преимуществ и предпочтительных приложений. В целом, SPI лучше подходит для приложений с высокой скоростью и низким энергопотреблением, а I2C лучше подходит для связи с большим количеством периферийных устройств и динамического изменения роли главного устройства среди периферийных устройств на шине I2C. И SPI, и I2C являются надежными и стабильными протоколами связи для встраиваемых приложений, которые хорошо подходят для встраиваемых систем.