Как использовать функции во встроенном программном обеспечении (прошивках)
В данной статье объясняется, почему вы должны использовать функции в своем коде на языке C, и обсуждаются ситуации, в которых функции особенно полезны.
Как и другие различные формы организации, функции изначально влекут за собой дополнительные усилия и продуманность. В долгосрочной перспективе мы сэкономим время и труд, не говоря уже о стрессе, если выработаем привычку писать код, который включает в себя щедрое использование функций.
Нет никаких сомнений в том, что мне было бы довольно просто взять каждый полученный мной документ и положить его на одну из нескольких стопок, разбросанных по моему столу. Однако в конечном счете эта простая организационная схема создаст все виды трудностей, даже если груды аккуратно расположены и маркированы наклейками с примечаниями. Точно так же, когда я думаю о проекте встроенного программного обеспечения, может показаться, что самый прямой и безболезненный путь к работающему прототипу – это относительно «безфункционный» исходный файл, который делает именно то, что мне нужно, и ничего больше. Время от времени этот подход имеет смысл, но в целом я считаю его недальновидным решением.
Выгоды от использования функций
Модульность
Код является модульным, если он состоит в основном из блоков кода (или «модулей»), которые выполняют четко определенные задачи и работают независимо друг от друга. Модульность помогает вам поддерживать ваш код организованным, многократно корректировать детали реализации и включать одинаковые функциональные возможности в различные части вашего проекта.
По моему опыту разработки встроенного ПО (прошивок), умеренное (а не навязчивое) использование функций C обеспечивает именно то, что нужно для модульности. Функция создает хороший модуль, если вы разрабатываете его в соответствии с принципом «черного ящика»: вы исходите из того, что другие части кода не знают, что происходит внутри функции. Всё, что они знают, – это задача, которую выполняет функция, и, если применимо, интерфейс функции, то есть входные и выходные параметры.
Идея заключается в том, что если код вызывает правильную функцию и предоставляет допустимых входные данные, требуемая задача будет выполнена правильно, и код получит допустимые выходные данные. Модульность также включает в себя работу с противоположной ситуацией – если вызывается неправильная функция, программа не должна аварийно завершить работу, а если указаны неверные входные данные, функция должна распознать это и соответствующим образом обработать ошибку.
Читабельность
Я не могу переоценить важность написания кода, который легко читать и интерпретировать. Абсолютно верно, что процессор может не заботиться о ваших интуитивно понятных идентификаторах, визуально красивом применении заглавных букв, поясняющих комментариях, отступах и так далее. Но верно также и то, человек, пишущий код, – это человек, а не компьютер, и читаемый код делает разработку программного обеспечения быстрее, точнее, гибче и приятнее.
Функции – отличный способ сделать код более читабельным, потому что они содержат потенциально большое количество сложных операторов в одном интуитивно понятоно вызове функции. Это особенно важно в разработке встроенного ПО (прошивок), где операторы C могут быть особенно непонятными, поскольку они используются для изменения, казалось бы, случайных битов в регистрах специального назначения со сложными названиями.
Повторное использование
Это связано с модульностью, хотя в этом разделе я хочу подчеркнуть возможность включения кода из одного проекта в совершенно другой отдельный проект.
Если вы склонны многократно использовать микроконтроллеры от одного и того же производителя (я рекомендую эту практику, если вы хотите повысить производительность и снизить нагрузку на себя), вы почти наверняка обнаружите, что вы регулярно внедряете функциональные возможности, практически идентичные, тем которые вы использовали в прошлом.
Если вы последовательно организуете ваше встроенное ПО в виде автономных функций, вы накопите богатый запас проверенного кода, который можно перенести в новые проекты. Копирование и вставка всей функции, особенно той, которая была разработана в соответствии с моделью черного ящика, проще и менее подвержены ошибкам, чем копирование и вставка отдельных строк кода, которые могут чередоваться с операторами, которые не имеют отношения к выполняемой вам функциональности, которую вы пытаетесь повторить. Кроме того, если в исходном коде четко не указано, какие инструкции необходимы для данной операции, вы можете упустить важный оператор и тем самым создать серьезную неисправность или, что еще хуже, неуловимую ошибку.
Сотрудничество
Это также тесно связано с модульностью. Если вы разрабатываете прошивку как набор автономных модулей, другому инженеру будет проще изменить или расширить функциональность вашего кода.
Снова обращаясь к модели черного ящика, мы видим, что код, созданный кем-то другим, будет легко интегрирован в остальную часть проекта, если он написан как функция, которая выполняет требуемую задачу, правильно обрабатывает входные данные и правильно генерирует выходные данные.
Советы по использования функций во встроенном программном обеспечении на C
- Очень удобно иметь переносимые проверенные блоки кода для простых операций, которые могут понадобиться в самых разных приложениях. Я называю это «служебными» функциями. В эту категорию я включаю такие вещи, как таймеры (либо обеспечивающие фиксированную задержку, либо принимающие входной параметр в микросекундах или миллисекундах), а также функции, которые могут найти максимальное или минимальное значение в последовательности, вычислить среднее значение ряда, очистить массив (т.е. установить значения всех элементов в ноль) или определить, находится ли значение в пределах диапазона, определяемого нижней и верхней границами.
- Функции – хорошее место для сложных математических операций. Как только вы определите, что конкретное вычисление реализовано правильно, стоит улучшить читабельность, скрыв все эти математические подробности внутри вызова функции.
- Полезно определить функцию для каждого вида работы вашей прошивки (обычно в этих функциях будут вызовы функций). Это позволяет легко настроить элементарную операционную систему реального времени, которая реагирует на события, проверяя флаги состояний и вызывая соответствующую функцию. Следующая диаграмма передает общую идею, а конкретный пример показан во фрагменте кода.
while(1)
{
if(RxData_FLAG)
{
RxDATA_FLAG = FALSE;
ProcessRxData();
}
if(ADCReady_FLAG)
{
ADCReady_FLAG = FALSE;
ProcessADCData();
}
if(LCDTimerDone_FLAG)
{
LCDTimerDone_FLAG = FALSE;
UpdateLCD();
}
}
Заключение
Надеюсь, я продемонстрировал, что код «без функций» не так быстр и удобен, как может показаться на первый взгляд, и что щедрое применение возможностей функций в языке C делает разработку встроенных программ не только более эффективной, но и более приятной. Если у вас есть какие-либо темы, связанные с разработкой прошивок, которые вы хотели бы видеть в будущих статьях, напишите об этом в комментариях ниже.