Ресурсы по написанию эффективного кода C для микроконтроллеров? [закрыто]

15

Здесь нужна серьезная помощь. Я люблю программирование. В последнее время я читал кучу книг (таких как K & R) и статей / форумов для языка Си. Даже пытался заглянуть в код Linux (хотя я потерял с чего начать, но заглянуть в маленькие библиотеки помогло?).

Я начинал как программист на Java, а в Java это довольно круто; Если программы становятся слишком большими, разделите их на классы, а затем на функции. Руководящие принципы, как, держать код читабельным и добавлять комментарии. Используйте методы сокрытия информации и ООП. Некоторые из которых все еще применяются для C.

Я сейчас пишу на C и до сих пор заставляю программы работать так или иначе. Многие люди говорят о производительности / эффективности, алгоритме / дизайне, оптимизации и ремонтопригодности. Некоторые люди подчеркивают друг друга больше, чем другие, но для непрофессиональных разработчиков программного обеспечения вы часто слышите что-то вроде, например: Linux kernel dev не просто берет какой-либо код.

У меня такой вопрос: я планирую написать код для 8-битного микроконтроллера, не теряя ресурсов . Знайте, что я из истории Java, поэтому все уже не так ... ресурсы / книги / ссылки / советы будут высоко оценены. Производительность и размер теперь важны. Ресурсы / Трюки по эффективному (в рамках передового опыта) коду C для 8-битных микроконтроллеров?

Кроме того, inline assemblyиграет важную роль, а также придерживаться стандарта микроконтроллеров. Но есть ли общее правило эффективности, которое применимо ко всем?

Например: register unsigned int variable_name;предпочтительнее в charлюбое время. Или, uint8_t если вам не нужны большие цифры.

РЕДАКТИРОВАТЬ: Большое спасибо за все ответы и предложения. Я ценю все усилия по обмену знаниями.

Туз пик
источник
2
Это не часто случается с процессором x86, но на микроконтроллере, если вы хотите быть уверенным, что получаете все до последней капли производительности, вы, вероятно, захотите использовать сборку.
Рей Миясака
3
@Rei: Созданная вручную сборка редко, если вообще когда-либо, использует меньше памяти и работает быстрее, чем современные компиляторы Си.
Кодирование
1
@mattnz По современному, о каком новом ты говоришь? Честно говоря, я не писал код для микроконтроллера почти десять лет.
Рей Миясака
2
Один простой совет: в микроконтроллерах все еще, как правило, верно, что «если это проще, то быстрее». На сложных чипах (ARM и выше) аппаратное обеспечение выполняет так много оптимизаций, что вы никогда не узнаете, пока не протестируете.
Хавьер
1
@ReiMiyasaka с огромным увеличением стоимости обслуживания. Хороший компилятор C может создать почти такой же код, как и опытный программист.

Ответы:

33

У меня есть более 20 лет встроенных систем, в основном 8 и 16 микро. Краткий ответ на ваш вопрос такой же, как и для любой другой разработки программного обеспечения - не оптимизируйте, пока не узнаете, что вам нужно, и затем не оптимизируйте, пока не узнаете, что нужно оптимизировать. Сначала напишите свой код, чтобы он был надежным, читаемым и обслуживаемым. Преждевременная оптимизация является такой же, если не большей, проблемой во встроенных системах

Когда вы программируете «без потерь ресурсов», считаете ли вы свое время ресурсом? Если нет, кто платит вам за ваше время, и если никто, у вас есть что-нибудь лучше сделать с этим. Однажды выбор, который должен сделать любой разработчик встраиваемых систем, - это стоимость оборудования против стоимости времени разработки. Если вы будете поставлять 100 единиц, использовать более крупный микро, по 100 000 единиц, экономия 1,00 долл. США за единицу равна экономии 1 года на разработку программного обеспечения (без учета времени выхода на рынок, альтернативных издержек и т. Д.), При стоимости 1 миллион единиц. получить окупаемость инвестиций за то, что помешан на использовании ресурсов, но будьте осторожны, потому что многие встраиваемые проекты никогда не делали отметку в 1 миллион, потому что они предназначены для продажи 1 миллиона (Высокие начальные инвестиции с низкими производственными затратами), и обанкротились до того, как они туда попали.

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

a) Стек - обычно у вас небольшой размер стека и часто ограниченный размер кадра стека. Вы должны знать о том, каково ваше использование стека во все времена. Имейте в виду, проблемы со стеком вызывают некоторые самые коварные дефекты.

б) Куча - опять же, кучи небольшого размера, поэтому будьте осторожны с необоснованным выделением памяти. Фрагментация становится проблемой. С этими двумя вы должны знать, что вы делаете, когда у вас заканчивается - это не происходит в большой системе из-за подкачки ОС. т.е. когда malloc возвращает NULL, вы проверяете это и что делаете. Каждый просвирник нуждается в проверке и обработчике, раздувании кода? Как руководство - не используйте его, если есть альтернатива. По этим причинам большинство небольших систем не используют динамическую память.

c) Аппаратные прерывания - Вы должны знать, как обрабатывать их безопасно и своевременно. Вы также должны знать, как сделать безопасный повторный ввод кода. Например, стандартные библиотеки C обычно не являются повторно входящими, поэтому их не следует использовать внутри обработчиков прерываний.

г) Сборка - почти всегда преждевременная оптимизация. Максимум небольшое количество (встроенное) необходимо для достижения того, чего С не может сделать. В качестве упражнения напишите небольшой метод ручной сборки (с нуля). Сделайте то же самое в C. Измерьте производительность. Бьюсь об заклад, C будет быстрее, я знаю, что он будет более читабельным, обслуживаемым и расширяемым. Теперь для второй части упражнения - напишите полезную программу на ассемблере и C.
Как еще одно упражнение, посмотрите, какая часть ядра Linux является ассемблером, а затем прочитайте параграф ниже о ядре linux.

Стоит знать, как это сделать, может быть, даже стоит быть знатоком языков для одного или двух общих микро.

e) «register unsigned int variable_name», «register» является и всегда было подсказкой компилятору, а не инструкцией, еще в начале 70-х (40 лет назад) это имело смысл. В 2012 году это пустая трата клавиш, так как компиляторы настолько умны, а инструкции по микросхемам так сложны.

Возвращаясь к вашему комментарию о Linux - проблема, с которой вы здесь столкнулись, заключается в том, что мы говорим не просто о миллионах устройств, мы говорим о сотнях миллионов с временем жизни навсегда. Инженерное время и стоимость, чтобы сделать его настолько оптимальным, насколько это возможно, стоит того. Хотя это хороший пример наилучшей инженерной практики, для большинства разработчиков встроенных систем было бы коммерческим самоубийством быть настолько педантичным, насколько этого требует ядро ​​linux.

mattnz
источник
4
mattnz: это один из самых красивых ответов на сайтах .stackexchange.
Ахмед Масуд
1
Я не могу улучшить этот ответ. Я мог бы только добавить, что вставка ассемблерного кода редко имеет смысл для производительности, но это может иметь смысл для таких вещей, как ковырять микросхемы ввода-вывода или другие аппаратные трюки, которые могут быть нелегко сделать в C.
Майк Данлавей
@mattnz Спасибо за удачный ответ. +1
AceofSpades
1
@MikeDunlavey ассемблер иногда необходим для точного выбора времени. Только что закончил наложение видео, которое использует битовую синхронизацию для генерации видео NTSC на выводе ввода / вывода, время в терминах - высокое напряжение для 3-тактовых циклов, затем низкое для 6, затем ....
Мартин Беккет
@Martin: это имеет смысл. Я давно не программировал на этом уровне.
Майк Данлавей
3

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

Кроме того, полезный совет очень зависит от ваших ограничений - какую систему вы строите и какой процессор используете? Это сложная система реального времени? Сколько памяти у вас есть для кода и данных? Поддерживает ли он изначально все операции C (в частности, умножение и деление) и для каких типов? В более общем плане: прочитайте всю таблицу данных и поймите это.

Самый важный совет: будь проще.

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

Кроме того, не переусердствуйте (то, что разработчики Java / C # имеют тенденцию делать): пишите простой процедурный код, не слишком много слоев. Абстракция имеет цену!

Познакомьтесь с идеей использования глобальных переменных и goto [очень полезно для очистки при отсутствии исключений];)

Если вам приходится иметь дело с прерываниями, читайте о повторном входе. Написание реентерабельного кода очень нетривиально.

zvrba
источник
3

Я согласен с ответом Мэтнза - по большей части. Я начал программировать на 8085 более 30 лет назад, затем на Z80, затем быстро перешел на 8031. После этого я перешел на микроконтроллеры серии 68300, затем 80x86, XScale, MXL и (на последнее время) 8-битную PICS, которую я думаю, я прошел полный круг. Напомню, что я могу сказать, что FAE в нескольких крупных производителях микропроцессоров все еще используют ассемблер, хотя и объектно-ориентированным способом для целенаправленного повторного использования кода.

В утвержденном ответе я не вижу обсуждения типа целевого процессора и / или предлагаемой архитектуры. Это 0,50 $ 8 битер с ограниченной памятью? Это ядро ​​ARM9 с конвейерной передачей и 8Meg вспышки? Память сопроцессора управления? Будет ли у него ОС? A while (1) цикл? Потребительское устройство с начальным производственным пробегом 100 000 единиц? Начинающая компания с большими идеями и мечтами?

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

И у меня никогда не было генерального директора, вице-президента по инжинирингу, клиента, который не заставлял меня пытаться засунуть галлон в контейнер кварта или сэкономить 0,05 $ с помощью программного решения для решения аппаратной проблемы (эй, это всего лишь программное обеспечение). правильно? что так сложно?). Оптимизация памяти (кода или данных) всегда будет учитываться.

Моя точка зрения заключается в том, что если вы посмотрите на проект с точки зрения чистого программирования, вы получите более узкое решение. Mattnz имеет право - заставить его работать, затем заставить его работать быстрее, меньше, лучше, но вам все равно нужно потратить МНОГО времени на требования и результаты, прежде чем вы даже подумаете о кодировании.

Gio
источник
Привет, Гио, пожалуйста, избегай ненужного HTML в своих постах и ​​используй вместо этого синтаксис Markdown . Чтобы <br />вы могли просто нажать Enter, а для абзацев просто оставить пустую строку между ними. Также, когда вы ссылаетесь на другой ответ, пожалуйста, добавьте ссылку на него. На данный момент может быть только несколько ответов, но их может быть больше, распределенных по многим страницам, и не очень понятно, какой ответ вы имеете в виду. Проверьте историю изменений, чтобы увидеть мои правки.
Яннис
@Gio Спасибо за упоминание других важных факторов. +1 :)
AceofSpades
+1 - Приятное расширение моего ответа.
Mattnz
1

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

Тем не менее, я хотел бы добавить, что хотя строгого ключевого слова «Class» в C не существует - довольно просто думать с точки зрения объектно-ориентированного программирования на C, даже если вы близки к аппаратному обеспечению.

Вы можете рассмотреть этот ответ: ОО лучшие практики для программ на Си, который объясняет этот момент.

Вот некоторые ресурсы, которые помогут вам написать хороший объектно-ориентированный код на C.

а. Объектно-ориентированное программирование на C
b. это хорошее место, где люди обмениваются идеями
в. и вот полная книга

Еще один хороший ресурс, который я хотел бы предложить вам:

Серия «Пиши великий код» . Это двухтомная книга. Первая книга охватывает очень важные аспекты машин на работах более низкого уровня. Вторая книга, о которой идет речь, - «Мышление низкого уровня - написание высокого уровня»

Дипан Мехта
источник
1

У вас есть несколько вопросов. сначала вы хотите, чтобы этот проект / код был переносимым? Переносимость снижает производительность и размер. Может ли используемая платформа и задачи, которые вы реализуете, переносить избыточный размер и более низкую производительность?

Да, абсолютно на 8-битной машине, возвращающей беззнаковые символы вместо беззнаковых целочисленных значений или шорт, это один из способов улучшить производительность и размер. Аналогично, на 16-битной машине используются неподписанные шорты и 32-битные машины без знака. Однако вы можете легко увидеть, что если вы, например, везде использовали неподписанные целочисленные значения для переносимости всей системы, которая захватывает (например, ARM продвигается на рынки с наименьшим энергопотреблением и наименьшее количество устройств), то этот код является огромной проблемой. 8-битный микро. Конечно, вы можете просто использовать unsigned без int, short или char и позволить компилятору выбрать оптимальный размер.

Не только встроенная сборка, но и язык ассемблера в целом. Встроенная сборка очень непереносима и ее сложнее написать, чем просто вызов функции asm. да, вы записываете настройку и возврат вызовов, но за счет упрощения разработки, лучшего обслуживания и контроля. Правило все еще применяется, пишите его только в asm, если вам действительно нужно, выполнили ли вы работу, чтобы сделать вывод, что вывод компилятора - это ваша проблема в этой области и какой выигрыш в производительности вы можете получить, выполнив его вручную. Затем вернемся к переносимости и обслуживанию, как только вы начнете смешивать C и asm, и каждый раз, когда вы смешиваете C и asm, вы можете повредить переносимости и сделать проект менее управляемым в зависимости от того, кто еще работает над ним или если это это продукт, который вы разрабатываете сейчас, и кто-то еще должен поддерживать в будущем. После того, как вы сделали этот анализ, вы автоматически знаете, нужно ли вам идти в линию или выполнять прямую сборку. Я работаю в этой области более 25 лет, каждый день пишу смеси C и Asm, живу на аппаратном / программном уровне и / или вообще не использую встроенный ассемблер. Это редко стоит усилий, слишком специфично для компилятора, я пишу не специфичный для компилятора код везде, где это возможно (почти везде).

Ключ ко всему вашему вопросу состоит в том, чтобы разобрать ваш код на C. Узнайте, что компилятор делает с вашим кодом, и со временем, если вы того пожелаете, вы можете научиться манипулировать компилятором для генерации кода, который вы хотите, не прибегая к использованию asm. Со временем вы сможете научиться манипулировать компилятором, чтобы создавать эффективный код для нескольких целей, делая код более переносимым, не прибегая к asm. У вас не должно возникнуть проблем с пониманием того, почему unsigned char работает лучше как возврат состояния из функции, чем unsigned in на 8-битном микро, так же как unsigned char становится более дорогостоящим в 16 и 32-битных системах (некоторые архитектуры помогают вам некоторые не)

Все некоторые 8-битные микроконтроллеры очень недружелюбны по отношению к компилятору, и ни один компилятор не создает хороший код. Нет достаточного спроса для создания рынка компиляторов для этих устройств, чтобы создать отличный компилятор для этих целей, поэтому имеющиеся там компиляторы привлекают больше программистов, не являющихся ассемблерами, и не потому, что компилятор лучше, чем рукописные ассемблеры. , Попадание в этот мир руки и руки меняет эту модель, поскольку у вас есть цели, у которых действительно есть компиляторы, над которыми было проделано много работы, компиляторы, которые производят довольно хороший код, и т. д. Для микросхем с такими процессорами у вас, конечно, еще есть В случае, когда вам нужно перейти к asm, но это не так часто, гораздо проще просто сказать компилятору, что вы хотите, чтобы он делал, чем не использовать его. Обратите внимание, что манипулирование компилятором не является некрасивым, нечитаемым кодом, на самом деле это противоположный, приятный, чистый, простой код, возможно, переставляющий несколько элементов. Контролируя размер ваших функций и количество параметров, такие вещи имеют огромное значение в выводе компилятора. Избегайте хитроумных функций компилятора или языка KISS, сохраняйте его простым, глупым, часто создавая гораздо лучший и быстрый код.

Старожил
источник
Вы не указываете тип продукции, которую вы производите, я бы предположил, что это либо очень большой объем, низкая маржа, либо конкретная ниша на рынке с безумной прибылью. Это очень важно для принятия решения на бизнес-уровне, следует ли вам использовать небольшой 8-битный микро и созданный вручную ассемблер или больший микро и C. В ответ на ваш (удаленный?) Комментарий - я работаю с 8-битными микро, однако мы начните с большего, чем нужно, микросхемы и пересматривайте ее только тогда, когда и если стоимость спецификации становится проблемой) Время выхода на рынок, стоимость эксплуатации и амортизированные затраты на разработку позволяют нам добавить 10 или 20 центов к спецификации.
Mattnz