Как использовать функции во встроенном программном обеспечении (прошивках)

Добавлено 30 мая 2019 в 00:06

В данной статье объясняется, почему вы должны использовать функции в своем коде на языке C, и обсуждаются ситуации, в которых функции особенно полезны.

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

Нет никаких сомнений в том, что мне было бы довольно просто взять каждый полученный мной документ и положить его на одну из нескольких стопок, разбросанных по моему столу. Однако в конечном счете эта простая организационная схема создаст все виды трудностей, даже если груды аккуратно расположены и маркированы наклейками с примечаниями. Точно так же, когда я думаю о проекте встроенного программного обеспечения, может показаться, что самый прямой и безболезненный путь к работающему прототипу – это относительно «безфункционный» исходный файл, который делает именно то, что мне нужно, и ничего больше. Время от времени этот подход имеет смысл, но в целом я считаю его недальновидным решением.

Выгоды от использования функций

Модульность

Код является модульным, если он состоит в основном из блоков кода (или «модулей»), которые выполняют четко определенные задачи и работают независимо друг от друга. Модульность помогает вам поддерживать ваш код организованным, многократно корректировать детали реализации и включать одинаковые функциональные возможности в различные части вашего проекта.

По моему опыту разработки встроенного ПО (прошивок), умеренное (а не навязчивое) использование функций C обеспечивает именно то, что нужно для модульности. Функция создает хороший модуль, если вы разрабатываете его в соответствии с принципом «черного ящика»: вы исходите из того, что другие части кода не знают, что происходит внутри функции. Всё, что они знают, – это задача, которую выполняет функция, и, если применимо, интерфейс функции, то есть входные и выходные параметры.

Рисунок 1 – Функция как «черный ящик»
Рисунок 1 – Функция как «черный ящик»

Идея заключается в том, что если код вызывает правильную функцию и предоставляет допустимых входные данные, требуемая задача будет выполнена правильно, и код получит допустимые выходные данные. Модульность также включает в себя работу с противоположной ситуацией – если вызывается неправильная функция, программа не должна аварийно завершить работу, а если указаны неверные входные данные, функция должна распознать это и соответствующим образом обработать ошибку.

Читабельность

Я не могу переоценить важность написания кода, который легко читать и интерпретировать. Абсолютно верно, что процессор может не заботиться о ваших интуитивно понятных идентификаторах, визуально красивом применении заглавных букв, поясняющих комментариях, отступах и так далее. Но верно также и то, человек, пишущий код, – это человек, а не компьютер, и читаемый код делает разработку программного обеспечения быстрее, точнее, гибче и приятнее.

Функции – отличный способ сделать код более читабельным, потому что они содержат потенциально большое количество сложных операторов в одном интуитивно понятоно вызове функции. Это особенно важно в разработке встроенного ПО (прошивок), где операторы C могут быть особенно непонятными, поскольку они используются для изменения, казалось бы, случайных битов в регистрах специального назначения со сложными названиями.

Повторное использование

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

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

Рисунок 2 – Повторное использование кода функций из одного проекта в других проектах
Рисунок 2 – Повторное использование кода функций из одного проекта в других проектах

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

Сотрудничество

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

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

Советы по использования функций во встроенном программном обеспечении на C

  • Очень удобно иметь переносимые проверенные блоки кода для простых операций, которые могут понадобиться в самых разных приложениях. Я называю это «служебными» функциями. В эту категорию я включаю такие вещи, как таймеры (либо обеспечивающие фиксированную задержку, либо принимающие входной параметр в микросекундах или миллисекундах), а также функции, которые могут найти максимальное или минимальное значение в последовательности, вычислить среднее значение ряда, очистить массив (т.е. установить значения всех элементов в ноль) или определить, находится ли значение в пределах диапазона, определяемого нижней и верхней границами.
  • Функции – хорошее место для сложных математических операций. Как только вы определите, что конкретное вычисление реализовано правильно, стоит улучшить читабельность, скрыв все эти математические подробности внутри вызова функции.
  • Полезно определить функцию для каждого вида работы вашей прошивки (обычно в этих функциях будут вызовы функций). Это позволяет легко настроить элементарную операционную систему реального времени, которая реагирует на события, проверяя флаги состояний и вызывая соответствующую функцию. Следующая диаграмма передает общую идею, а конкретный пример показан во фрагменте кода.
Рисунок 3 – Общая идея реализации элементарной операционной системы реального времени, реагирующей на события
Рисунок 3 – Общая идея реализации элементарной операционной системы реального времени, реагирующей на события
while(1)
{
  if(RxData_FLAG)
  {
    RxDATA_FLAG = FALSE;
    ProcessRxData();
  }

  if(ADCReady_FLAG)
  {
    ADCReady_FLAG = FALSE;
    ProcessADCData();
  }

  if(LCDTimerDone_FLAG)
  {
    LCDTimerDone_FLAG = FALSE;
    UpdateLCD();
  }
}

Заключение

Надеюсь, я продемонстрировал, что код «без функций» не так быстр и удобен, как может показаться на первый взгляд, и что щедрое применение возможностей функций в языке C делает разработку встроенных программ не только более эффективной, но и более приятной. Если у вас есть какие-либо темы, связанные с разработкой прошивок, которые вы хотели бы видеть в будущих статьях, напишите об этом в комментариях ниже.

Теги

MCUВысокоуровневые языки программированияВычисления во встраиваемых системахМикроконтроллерРазработка ПО для встраиваемых системЯзык C

На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.

В случае комментирования в качестве гостя (без регистрации на disqus.com) для публикации комментария требуется время на премодерацию.