Я не новичок в программировании, и я даже работал с некоторыми низкоуровневыми C и ASM на AVR, но я действительно не могу разобраться с более масштабным проектом встроенного C.
Будучи перерожденным философией Ruby TDD / BDD, я не могу понять, как люди пишут и тестируют подобный код. Я не говорю, что это плохой код, я просто не понимаю, как это может работать.
Я хотел больше узнать о программировании на низком уровне, но я действительно не представляю, как к этому подойти, поскольку это выглядит как совершенно другое мышление, к которому я привык. У меня нет проблем с пониманием арифметики указателей или с тем, как работает распределение памяти, но когда я вижу, как выглядит сложный код C / C ++ по сравнению с Ruby, это просто кажется невероятно сложным.
Поскольку я уже заказал себе плату Arduino, я бы хотел больше узнать о каком-либо низком уровне C и действительно понять, как все делать правильно, но, похоже, ни одно из правил языков высокого уровня не применимо.
Можно ли вообще использовать TDD на встроенных устройствах или при разработке драйверов или таких вещей, как собственный загрузчик и т. Д.?
источник
Ответы:
Прежде всего, вы должны знать, что пытаться понять код, который вы не написали, в 5 раз сложнее, чем писать его самостоятельно. Вы можете изучать C, читая производственный код, но это займет намного больше времени, чем обучение на практике.
Это навык; Вы становитесь лучше в этом. Большинство программистов на C не понимают, как люди используют Ruby, но это не значит, что они не могут.
Ну, есть книги на эту тему:
Если шмель может это сделать, вы тоже можете!
Имейте в виду, что применение практики из других языков обычно не работает. TDD довольно универсален, хотя.
источник
Большое разнообразие ответов здесь ... в основном решение проблемы различными способами.
Более 25 лет я пишу встроенное низкоуровневое программное обеспечение и прошивки на разных языках - в основном на C (но с путями в Ada, Occam2, PL / M и множестве ассемблеров).
После долгих размышлений, проб и ошибок я остановился на методе, который довольно быстро получает результаты и довольно легко создает тестовые обертки и жгуты (где они ДОБАВЛЯЮТ ЗНАЧЕНИЕ!)
Метод идет примерно так:
Напишите модуль кода драйвера или аппаратной абстракции для каждого основного периферийного устройства, которое вы хотите использовать. Также напишите один, чтобы инициализировать процессор и настроить все (это создает дружественную среду). Как правило, на небольших встроенных процессорах (например, ваш AVR) может быть от 10 до 20 таких устройств, все маленькие. Это могут быть единицы для инициализации, аналого-цифрового преобразования в немасштабированные буферы памяти, побитовый вывод, ввод с помощью кнопки (без выборки, только выборка), драйверы широтно-импульсной модуляции, UART / простые последовательные драйверы, использующие прерывания и небольшие буферы ввода / вывода. Может быть еще несколько - например, драйверы I2C или SPI для EEPROM, EPROM или других устройств I2C / SPI.
Затем для каждого из модулей аппаратной абстракции (HAL) / драйвера я пишу тестовую программу. Это зависит от последовательного порта (UART) и инициализации процессора - поэтому первая тестовая программа использует только эти 2 модуля и выполняет только некоторые основные операции ввода и вывода. Это позволяет мне проверить, что я могу запустить процессор и что у меня есть базовая поддержка последовательного ввода-вывода поддержки отладки. Как только это сработает (и только тогда), могу ли я затем разработать другие тестовые программы HAL, построив их поверх известных хороших модулей UART и INIT. Поэтому у меня могут быть тестовые программы для чтения побитовых входных данных и отображения их в удобной форме (шестнадцатеричной, десятичной или любой другой) на моем терминале последовательной отладки. Затем я могу перейти к более крупным и более сложным вещам, таким как тестовые программы EEPROM или EPROM - я делаю большинство этих меню управляемыми, чтобы выбрать тест для запуска, запустить его и увидеть результат. Я не могу СКАРИТЬ это, но обычно я не
После того, как все мои HAL запущены, я нахожу способ получить регулярный таймер. Это обычно со скоростью от 4 до 20 мс. Это должно быть регулярно, генерируется в прерывании. Как правило, это можно сделать с помощью опрокидывания / переполнения счетчиков. Затем обработчик прерываний увеличивает размер семафора в байтах. На этом этапе вы также можете возиться с управлением питанием, если вам нужно. Идея семафора состоит в том, что если его значение> 0, вам нужно запустить «основной цикл».
EXECUTIVE запускает основной цикл. Он просто ожидает, что этот семафор станет ненулевым (я абстрагирую эту деталь). На этом этапе вы можете поиграть со счетчиками, чтобы подсчитать эти тики (потому что вы знаете частоту тиков), и поэтому вы можете установить флаги, показывающие, является ли текущий тик руководителя интервалом в 1 секунду, 1 минуту и другие общие интервалы, которые вы используете. Возможно, захотите использовать. Как только руководитель узнает, что семафор> 0, он выполняет один проход через каждую функцию «обновления» процессов «приложения».
Процессы приложения эффективно расположены рядом друг с другом и регулярно запускаются с помощью галочки «обновление». Это просто функция, вызываемая исполнительной властью. Это, по сути, многозадачность для бедняков с очень простой домашней ОСРВ, которая опирается на все приложения, входящие, выполняющие небольшую часть работы и выходящие. Приложения должны поддерживать свои собственные переменные состояния и не могут выполнять долгосрочные вычисления, потому что нет приоритетной операционной системы для обеспечения справедливости. Очевидно, что время выполнения приложений (совокупно) должно быть меньше, чем период основного тика.
Вышеупомянутый подход легко расширяется, поэтому вы можете добавить такие вещи, как стеки связи, которые работают асинхронно, и затем сообщения приложений могут быть доставлены приложениям (вы добавляете новую функцию для каждого, которая называется rx_message_handler), и вы пишете диспетчер сообщений, который показывает из какого приложения отправлять).
Этот подход работает практически для любой коммуникационной системы, которую вы хотите назвать - он может (и уже работал) для многих проприетарных систем, систем связи с открытыми стандартами, он даже работает для стеков TCP / IP.
Он также имеет преимущество в том, что он состоит из модульных элементов с четко определенными интерфейсами. Вы можете вставлять и вынимать части в любое время, заменяя их разными. В каждой точке пути вы можете добавить тестовые жгуты или манипуляторы, которые опираются на известные хорошие детали нижнего слоя (материал ниже). Я обнаружил, что примерно от 30% до 50% дизайна могут получить пользу от добавления специально написанных модульных тестов, которые обычно довольно легко добавляются.
Я сделал еще один шаг вперед (идея, которую я получил от кого-то еще, кто сделал это) и заменил слой HAL аналогом для ПК. Так, например, вы можете использовать C / C ++ и winforms или аналогичные на ПК и, написав код ВНИМАТЕЛЬНО, вы можете эмулировать каждый интерфейс (например, EEPROM = файл диска, считываемый в память ПК), а затем запускать все встроенное приложение на ПК. Возможность использования дружественной среды отладки может сэкономить огромное количество времени и усилий. Только действительно большие проекты могут оправдать такое количество усилий.
Вышеприведенное описание не является уникальным для того, как я работаю на встроенных платформах - я сталкивался с многочисленными коммерческими организациями, которые делают подобное. То, как это делается, обычно сильно отличается в реализации, но принципы часто во многом совпадают.
Я надеюсь, что вышеизложенное дает некоторую изюминку ... этот подход работает для небольших встроенных систем, которые работают в нескольких килобайтах с агрессивным управлением батареями, вплоть до монстров 100К или более линий источников, которые работают постоянно. Если вы используете «встроенный» в большой ОС, такой как Windows CE или около того, то все вышеперечисленное совершенно несущественно. Но это не РЕАЛЬНОЕ встроенное программирование, во всяком случае.
источник
Код, который имеет долгую историю постепенной разработки и оптимизации для нескольких платформ, таких как примеры, которые вы выбрали, обычно труднее читать.
Суть C в том, что он на самом деле способен охватывать платформы в широком диапазоне API и аппаратной производительности (и их отсутствия). MacVim работал быстро на машинах с более чем в 1000 раз меньшей производительностью памяти и процессора, чем у типичного смартфона сегодня. Может ли ваш код на Ruby? Это одна из причин, по которой это может выглядеть проще, чем выбранные вами зрелые C-примеры.
источник
Я нахожусь в обратном положении: большую часть последних 9 лет я работал программистом на C и недавно работал над некоторыми интерфейсами Ruby on Rails.
В C я работаю в основном с пользовательскими системами среднего размера для управления автоматизированными складами (типичная стоимость от нескольких сотен тысяч фунтов до нескольких миллионов). Примером функциональности является пользовательская база данных в памяти, взаимодействующая с оборудованием с некоторыми короткими требованиями времени отклика и более высоким уровнем управления рабочим процессом хранилища.
Прежде всего, я могу сказать, что мы не делаем TDD. Я несколько раз пытался ввести модульные тесты, но в Си это больше проблем, чем стоит - по крайней мере, при разработке программного обеспечения на заказ. Но я бы сказал, что TDD гораздо менее необходим в C, чем Ruby. Главным образом, это только потому, что C компилируется, и если он компилируется без предупреждений, вы уже выполнили довольно похожий объем тестирования с автоматически генерируемыми тестами скаффолдинга rspec в Rails. Рубин без юнит-тестов невозможен.
Но я бы сказал, что C не должен быть таким сложным, как это делают некоторые люди. Большая часть стандартной библиотеки C - это беспорядок непонятных имен функций, и многие программы на C следуют этому соглашению. Я рад сообщить, что у нас этого нет, и на самом деле у нас есть много оберток для стандартных функций библиотеки (ST_Copy вместо strncpy, ST_PatternMatch вместо regcomp / regexec, CHARSET_Convert вместо iconv_open / iconv / iconv_close и т. Д.). Наш внутренний код на C читается лучше, чем большинство других вещей, которые я читал.
Но когда вы говорите, что правила из других языков более высокого уровня не применяются, я бы не согласился. Много хорошего кода на языке "чувствует" объектно-ориентированный. Вы часто видите шаблон инициализации дескриптора ресурса, вызова некоторых функций, передающих дескриптор в качестве аргумента, и в конечном итоге освобождения ресурса. Действительно, принципы проектирования объектно-ориентированного программирования во многом основаны на том, что люди делали на процедурных языках.
Временами, когда C становится действительно сложным, часто приходится делать такие вещи, как драйверы устройств и ядра ОС, которые по сути являются очень низкоуровневыми. Когда вы пишете систему более высокого уровня, вы также можете использовать функции более высокого уровня C и избежать сложности низкого уровня.
Одна очень интересная вещь, которую вы, возможно, захотите просмотреть, это исходный код C для Ruby. В документации по Ruby API (http://www.ruby-doc.org/core-1.9.3/) вы можете щелкнуть и посмотреть исходный код для различных методов. Интересно то, что этот код выглядит довольно красиво и элегантно - он не выглядит таким сложным, как вы можете себе представить.
источник
Я отделил код, зависящий от устройства, от кода, независимого от устройства, а затем протестировал код, не зависящий от устройства. С хорошей модульностью и дисциплиной, вы получите в основном хорошо проверенную кодовую базу.
источник
Там нет причин, почему вы не можете. Проблема в том, что не может быть хороших готовых платформ для модульного тестирования, как в других типах разработки. Ничего страшного. Это просто означает, что вы должны использовать «тестируемый» подход к тестированию.
Например, вам может потребоваться запрограммировать инструментарий для создания «ложных входов» для ваших аналого-цифровых преобразователей или, возможно, вам придется сгенерировать поток «ложных данных», чтобы ваше встроенное устройство могло на них реагировать.
Если вы сталкиваетесь с сопротивлением использованию слова «TDD», назовите его «DVT» (тест проверки проекта), что сделает EE более удобным для этой идеи.
источник
Можно ли вообще использовать TDD на встроенных устройствах или при разработке драйверов или таких вещей, как собственный загрузчик и т. Д.?
Некоторое время назад мне нужно было написать загрузчик первого уровня для процессора ARM. На самом деле есть один из парней, которые продают этот процессор. И мы использовали схему, где их загрузчик загружает наш загрузчик. Но это было медленно, так как нам нужно было прошить два файла в NOR flash вместо одного, нам нужно было встроить размер нашего загрузчика в первый загрузчик и перестраивать его каждый раз, когда мы меняли наш загрузчик и так далее.
Поэтому я решил интегрировать функции их загрузчика в наши. Поскольку это был коммерческий код, я должен был убедиться, что все работает как положено. Поэтому я изменил QEMU, чтобы эмулировать IP-блоки этого ЦП (не все, только те, которые касаются загрузчика), и добавил код в QEMU для «printf» всех операций чтения / записи в регистры, управляющие такими вещами, как контроллер PLL, UART, SRAM и скоро. Затем я обновил наш загрузчик для поддержки этого процессора, и после этого сравнил выходные данные, которые дают наш загрузчик и их на эмуляторе, это помогает мне обнаружить несколько ошибок. Он был написан частично на ассемблере ARM, частично на C. Также после этого модифицированный QEMU помог мне поймать одну ошибку, которую я не смог отловить с помощью JTAG и реального процессора ARM.
Так что даже с C и ассемблером вы можете использовать тесты.
источник
Да, это можно сделать TDD на встроенном программном обеспечении. Люди, говорящие, что это невозможно, не имеет отношения к делу или не применимы, не правы. Существует серьезная ценность, которую можно получить от встроенного TDD, как и с любым программным обеспечением.
Однако лучший способ сделать это - не запускать ваши тесты на целевой платформе, а абстрагировать ваши аппаратные зависимости, компилировать и запускать на хост-ПК.
Когда вы делаете TDD, вы будете создавать и запускать множество тестов. Вам нужно программное обеспечение, чтобы помочь вам сделать это. Вам нужен тестовый фреймворк, позволяющий быстро и легко сделать это с автоматическим обнаружением тестов и генерацией макетов.
Лучший вариант для C прямо сейчас - Ceedling. Вот пост о том, что я написал об этом:
http://www.electronvector.com/blog/try-embedded-test-driven-development-right-now-with-ceedling
И он построен в Ruby! Вам не нужно знать какой-либо Ruby, чтобы использовать его.
источник