Итак, вам дали плату, а в ней 4 навороченных умных периферийных чипа с собственными внутренними конфигурационными по SPI/I2C регистрами. Это могут быть такие чипы, как at24cxx, si4703, ksz8081, sx1262, wm8731, tcan4550, fda801, tic12400, ssd1306, dw1000, или drv8711. Не важно, какой конкретно чип. Все они работают по одному принципу. Прописываешь по проводному интерфейсу чиселки в их внутренние регистры и там внутри чипа заводится какая-то электрическая цепочка. Easy.  Допустим, что на GitHub драйверов для вашего I2C/SPI чипа нет или качество этих open sourсe драйверов оставляет желать лучшего. Как же оформить и собрать качественный драйвер для I2C/SPI/MDIO чипа? И почему это вообще важно? Смоки, тут не Вьетнам, это — боулинг, здесь есть правила. Понятное дело, что нужно, чтобы драйвер был модульным, поддерживаемым, тесто-пригодным, диагностируемым, понятным. Прежде всего надо понять, как организовать структуру файлов с драйвером. Это можно сделать так: Должна быть функция инициализации, обработчика в цикле, проверка Link(а), функции чтения и записи регистра по адресу. Плюс набор высокоуровневых функций для установки и чтения конкретных параметров. Это тот минимум минимумов, на котором большинство разработчиков складывает руки. Далее следует материал уровня аdvanced.  Это нужно для того, чтобы подчеркнуть тот факт, что к этому ISR коду надо относиться с особенной осторожностью. Например, это ядро программного таймера. Что этот код должен быть оптимизирован по быстродействию, что этот код сам не должен вызывать другие прерывания. В этом файле следует определить основные типы данных для данного программного компонента. Также определить объединения и битовые поля для каждого регистра.  Тут надо определить адреса регистров, перечисления. Это очень важно быстро найти файл с константами и отредактировать их, поэтому для констант делаем отдельный *.h файл. Каждый драйвер нуждается в энергонезависимых параметрах с настройками (битовая скорость, CAN-трансивера или несущая частота радиопередатчика). Именно эти настройки будут применятся при инициализации при старте питания. Параметры позволят существенно изменять поведение всего устройства без нужды пересборки всех сорцов. Просто прописали через CLI параметры и перезагрузились. И у вас новый функционал. Успех! Поэтому надо где-то указать как минимум тип данных и имя параметров драйвера. После старта питания надо как-то проинициализировать драйвер. Особенно при первом запуске, когда FlashFs/NVRAM ещё пустая. Для этого создаем отдельные файлы для конфигов по умолчанию. Это способствует методологии "код отдельно, конфиги отдельно". --Драйвер должен легко масштабироваться Конфиг и сам драйвер надо писать так, чтобы драйвер поддерживал сразу несколько экземпляров сущностей драйвера. Так драйвер можно будет масштабировать новыми узлами. У каждого взрослого компонента должна быть ручка для управления. В мире компьютеров исторически еще со времен UNIX (в 197x) такой "ручкой" является интерфейс командной строки (CLI) поверх UART. Поэтому создаем отдельные файлы для интерпретатора команд для каждого конкретного драйвера. Буквально 3-4 команды: инициализация, диагностика, get /set регистра. Так можно будет изменить логику работы драйвера в Run-Time. Вычитать сырые значения регистров, прописать конкретный регистр. Показать диагностику, серийный номер, ревизию , пулять пакеты в I2C, SPI, UART, MDIO и т. п. У каждого драйвера есть куча всяческих констант. Значения подобраны вендором обычно рандомно. Эти константы надо интерпретировать в строки для человека-понимания. Поэтому создается файл с Hash функциями. Суть проста: даешь бинарное значение константы и тут же получаешь её значение в виде текстовой строчки. Эти Hash функции как раз вызывает CLI(шка) и компонент логирования при логе инициализации board(ы). Драйвер должен быть покрыт модульными тестами (скрепы). Это позволит делать безопасное перестроение кода с целью его упрощения. Тесты нужны для отладки большого куска кода, который трудно проходить пошаговым отладчиком. Тесты позволят быстрее делать интеграцию. Помогут понять, что сломалось в случае ошибок. Т.е. тесты позволяют сэкономить время на отладке. Тесты будут поощрять вас писать более качественный  и структурированный код. Если в вашем коде нет модульных тестов, то не ждите к себе хорошего отношения. Так как код без тестов - это Филькина грамота.  Сборка из Make это самый мощный способ управлять модульностью и масштабируемостью любого кода. С make можно производить выборочную сборку драйвера в зависимости от располагаемых ресурсов на печатной плате. Код станет универсальным и переносимым. При сборке из Makefile(ов) надо для каждого логического компонента или драйвера вручную определять make файл. Make - это целый отдельный язык программирования со своими операторами и функциями. Спека GNU Make всего навсего это 224 страницы. Вот так должен примерно выглядеть код драйвера в папке с проектом: Со структурой драйвера определились. Теперь буквально несколько слов о функционале обобщенного драйвера. *9--Добавить xxxxx_dep.h файл с проверками зависимостей на фазе препроцессора. Это позволит отловить на стадии компиляции ошибки отсутствия драйверов, которые нужны для этого драйвера.  10--xxx.gvi файл на языке Graphviz явно указывающий зависимости внутри программного компонента. Это надо для авто генерации документации Подробнее про это можно почитать тут https://habr.com/ru/articles/765424/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 При каждом изменении в коде драйвера версию надо увеличивать. 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 для явного определения зависимостей.    Суммируя вышесказанное получается вот такой список необходимых файлов Это базис любого драйвера. Своего рода ортодоксально-каноническая форма. Или просто строительные леса. Остальной код зависит уже от конкретного ASIC чипа, будь это чип управления двигателем, беспроводной трансивер, RTC или простой датчик давления. Как видите, чтобы написать адекватный драйвер чипа надо учитывать достаточно много нюансов и проделать некоторую инфраструктурную работу. Не стесняйтесь разбивать драйвер на множество файлов. Это потом сильно поможет при custom(мизации), переносе и упаковке драйвера в разные проекты с разными ресурсами.  Если есть замечания на тему того, какими еще атрибутами должен обладать обобщенный драйвер периферийного I2C/SPI/MDIO чипа, то пишите в комментариях. Linkshttps://docs.google.com/spreadsheets/d/15NytV9HHFhiZxEC8OUhsROoK6hX6QQpPPZqc2VgSTUY/edit#gid=0  Embedded SW/Firmware Engineer