PIC32 против dsPIC против ARM против AVR, имеет ли значение архитектура, когда мы программируем на языке C? [закрыто]

В настоящее время мы используем 32-битный микроконтроллер PIC32. Он отлично подходит для наших нужд, но мы также изучаем другие микроконтроллеры, которые могут нам подойти лучше + у нас есть другие проекты, для которых мы выбираем MCU. Для этой цели мы выбрали микроконтроллер SAM DA на базе ARM, который является таким же 32-битным, но основан на ARM (более популярен, чем PIC32 — в отрасли).

Теперь для PIC32 мы используем MPLAB, но для ARM cortex-M0 мы будем использовать Atmel Studio. Мы будем использовать язык C на обеих платформах. Меня беспокоит то, что мы будем использовать два 32-битных микроконтроллера (от одной компании), но с разными архитектурами. Это потребует от нас изучения двух разных устройств и увеличит нашу «кривую обучения» + время доставки. Но, с другой стороны, я также думаю, что, поскольку мы будем использовать язык C в обоих случаях, кривая обучения для ARM не должна быть такой уж слышимой, и стоит также изучить этот процессор.

Мой главный вопрос заключается в том, насколько велика разница в архитектуре, когда мы программируем на языке C, поскольку она обеспечивает абстракцию внутренних компонентов микроконтроллера. И в чем основные отличия MPLAP и Atmel Studio , учитывая программирование на языке C.

Если с PIC32 все работает, то какой смысл переключаться? Даже если код будет полностью портирован (а этого не будет), все равно придется привыкать к новой цепочке инструментов и IDE. В чем смысл? Переключаться по религиозным причинам или быть «основанным на ARM» (или на чем-то еще) глупо. У вас должна быть веская причина, но вы нам ее не показали.
Я не спрашивал о переключении. Я говорил о выборе другой архитектуры для других проектов, поскольку мы работаем над несколькими проектами + есть возможности для улучшения нашего существующего дизайна. Основное внимание было уделено кривой обучения и проблемам при одновременной работе с двумя разными архитектурами.
Я обнаружил, что Atmel Studio обеспечивает лучшее время, чем видео MPLAB на YouTube.

Ответы (5)

Это довольно спорная тема. Могу сказать за себя (AVR, ARM, MSP430).

Отличие 1 (наиболее существенное) заключается в периферийных устройствах. Каждый из микроконтроллеров имеет одинаковые UART, SPI, таймеры и т. д. - просто имена регистров и биты разные. Большую часть времени это была основная проблема, с которой мне приходилось сталкиваться при перемещении кода между чипами. Решение: напишите свои драйверы с общим API, чтобы ваше приложение могло быть переносимым.

Отличие 2 — архитектура памяти. Если вы хотите поместить константы во флэш-память на AVR, вы должны использовать специальные атрибуты и специальные функции для их чтения. В мире ARM вы просто разыменовываете указатель, потому что существует единое адресное пространство (я не знаю, как с этим справляются маленькие PIC, но предположу, что они ближе к AVR).

Отличие 3 заключается в объявлении и обработке прерывания. avr-gccимеет ISR()макрос. У ARM есть только имя функции (например, someUART_Handler() — если вы используете заголовки CMSIS и код запуска). Векторы прерываний ARM могут быть размещены где угодно (включая ОЗУ) и изменены во время выполнения (очень удобно, если у вас есть, например, два разных протокола UART, которые можно переключать). AVR имеет только возможность использовать векторы либо в «основной флэш-памяти», либо в «разделе загрузчика» (поэтому, если вы хотите по-другому обрабатывать прерывания, вы должны использовать ifоператор).

Отличие 4 - спящие режимы и управление питанием. Если вам нужно минимальное энергопотребление, вам нужно использовать все функции микроконтроллера. Это может сильно различаться между MCU — некоторые из них имеют более грубые режимы энергосбережения, некоторые могут включать / отключать отдельные периферийные устройства. Некоторые микроконтроллеры имеют регулируемые регуляторы, поэтому вы можете запускать их с более низким напряжением на более низкой скорости и т. д. Я не вижу простого способа добиться такой же эффективности на микроконтроллере (скажем) с 3 глобальными режимами питания, а другой с 7 режимами питания и индивидуальное управление периферийными часами.

Самая важная вещь при заботе о переносимости — четкое разделение кода на аппаратно-зависимые (драйверы) и аппаратно-независимые (приложения) части. Вы можете разработать и протестировать последний на обычном ПК с фиктивным драйвером (например, консолью вместо UART). Это спасло меня много раз, так как 90% кода приложения было завершено до того, как прототип оборудования вышел из печи оплавления :)

На мой взгляд, плюсом ARM является "монокультура" - наличие множества компиляторов (gcc, Keil, IAR... и это лишь некоторые из них), множество бесплатных и официально поддерживаемых IDE (по крайней мере, для NXP, STM32, Silicon Labs, Nordic), множество инструментов отладки (SEGGER, особенно Ozone, ULINK, OpenOCD...) и множество поставщиков чипов (я даже не буду их называть). PIC32 в основном ограничен Microchip (но это имеет значение только в том случае, если вам не нравятся их инструменты.

Когда дело доходит до C-кода. Это на 99% то же самое, ifоператор тот же, цикл работает так же. Однако вы должны заботиться о собственном размере слова. Например, forцикл на AVR будет самым быстрым, если вы используете uint8_tсчетчик, а на ARM uint32_t— самый быстрый тип (или int32_t). ARM пришлось бы каждый раз проверять наличие 8-битного переполнения, если бы вы использовали меньший тип.

Выбор MCU и/или поставщика в целом в основном зависит от политики и логистики (если только у вас нет очень четких технических ограничений, например: высокая температура — используйте MSP430 или Vorago). Даже если приложение может работать на чем угодно и только 5% кода (драйверы) должны быть разработаны и поддерживаться в течение всего срока службы продукта — это все равно дополнительные расходы для компании. Во всех местах, где я работал, был любимый поставщик и линейка MCU (например, «выберите любой Kinetis, который вы хотите, если нет очень веской причины выбрать что-то другое»). Также помогает, если у вас есть другие люди, к которым можно обратиться за помощью, поэтому как менеджер я бы избегал иметь отдел разработки из 5 человек, где каждый использовал бы совершенно другой чип.

«AVR самый быстрый, если вы используете uint8_t для счетчика, в то время как на ARM uint32_t — самый быстрый тип (или int32_t). ARM пришлось бы каждый раз проверять 8-битное переполнение, если бы вы использовали меньший тип». вы можете использовать uint_fast8_t, если вам нужно не менее 8 бит.
@Michael - конечно, вы можете использовать типы _fast, но вы не можете рассчитывать на поведение переполнения. В моем gcc stdint.h у меня есть "typedef unsigned int uint_fast8_t", который в основном является uint32_t :)
Попытка написать API, который был бы эффективным, универсальным и полным, сложна, учитывая, что разные платформы имеют разные возможности. ЦП, вероятно, имеет меньшее значение, чем периферийные устройства и решения, принятые с их помощью. Например, некоторые устройства позволяют переконфигурировать различные периферийные устройства в любое время, самое большее за несколько микросекунд, в то время как для других может потребоваться несколько шагов, растянутых на сотни микросекунд или даже миллисекунд. Функцию API, предназначенную для первого шаблона, можно использовать в подпрограмме обработки прерывания, работающей на частоте 10 000 Гц, но...
... не может поддерживать такое использование на платформах, которые требуют распределения операций на сотни микросекунд. Я не знаю, почему разработчики аппаратного обеспечения, кажется, не очень стараются поддерживать семантику API «быстрая операция в любое время», но многие используют модель, которая синхронизирует отдельные операции, а не состояние, так что, если, например, запрос был передан на включить устройство, и код понимает, что его не нужно включать, код должен дождаться включения устройства, прежде чем он сможет отправить запрос на выключение. Плавная обработка этого в API добавляет серьезных сложностей.

Я использовал несколько MCU от четырех разных производителей. Основная работа каждый раз заново — знакомиться с периферией.

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

GPIO может быть очень сложным. Установка битов, очистка битов, переключение битов, включение/выключение специальных функций, три состояния. Затем вы получаете прерывания: любой фронт, рост, падение, низкий уровень, высокий уровень, самоочищение или нет.

Затем есть I2C, SPI, PWM, таймеры и еще два десятка типов периферии, каждый со своим собственным включением часов, и каждый раз регистры разные с новыми битами. Для всех них требуется много часов, чтобы прочитать техническое описание, как установить, какой бит и при каких обстоятельствах.

У последнего производителя было много примеров кода, которые я счел непригодными. Все было абстрагировано. Но когда я проследил его, код прошел через шесть! уровни вызовов функций для установки бита GPIO. Хорошо, если у вас есть процессор с частотой 3 ГГц, но не MCU с частотой 48 МГц. Мой код в конце был одной строкой:

GPIO->set_output = bit.

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

Итак, теперь у меня все работает, и я планирую НЕ переключаться снова, если только по очень-очень веской причине.

Все вышеперечисленное должно амортизироваться в зависимости от того, сколько продуктов вы продаете и сколько вы экономите. Продажа миллиона: экономия 0,10 для перехода на другой тип означает, что вы можете потратить 100 000 человеко-часов на программное обеспечение. Продав 1000, вы можете потратить только 100.

Лично поэтому я придерживаюсь ассемблера. Прекрасный бинарник, никакой абстракции.
Препроцессор C может довольно хорошо справляться с вещами, особенно в сочетании с встроенными функциями __builtin_constant. Если определить константы для каждого бита ввода-вывода в форме (номер порта * 32 + номер бита), можно написать макрос, для OUTPUT_HI(n)которого будет получен код, эквивалентный константе, такой как 0x6A, но вызовите простую подпрограмму, GPIOD->bssr |= 0x400;если это не постоянный. При этом большинство API-интерфейсов поставщиков, которые я видел, варьируются от посредственных до ужасных. nn

Это больше мнение/комментарий, чем ответ.

Вы не хотите и не должны программировать на C. C++, при правильном использовании , намного лучше. (Хорошо, я должен признать, что при неправильном использовании он намного хуже, чем C.) Это ограничивает вас чипами, которые имеют (современный) компилятор C++, который является примерно всем, что поддерживается GCC, включая AVR (с некоторые ограничения, filo упоминает о проблемах неоднородного адресного пространства), но исключая почти все PIC (PIC32 мог бы поддерживаться, но я пока не видел приличного порта).

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

Когда вы кодируете аппаратное обеспечение, вы можете либо использовать некоторый уровень абстракции (часто предоставляемый производителем), либо написать свой собственный (на основе таблицы данных и/или примера кода). Существующие абстракции IME C (mbed, cmsis, ...) часто функционально (почти) правильны, но ужасно терпят неудачу в производительности (проверьте старые разглагольствования о 6 уровнях косвенности для операции набора контактов), удобстве использования и переносимости. Они хотят предоставить вам всю функциональность конкретного чипа, который почти во всех случаях вам не понадобится и не будет вас волновать, и он привязывает ваш код к этому конкретному поставщику (и, возможно, к этому конкретному чипу).

Это то, где C++ может сделать намного лучше: если все сделано правильно, набор выводов может пройти через 6 или более уровней абстракции (потому что это делает возможным лучший (переносимый!) интерфейс и более короткий код), но предоставить интерфейс, независимый от цели. для простых случаев и по- прежнему приводит к тому же машинному коду, что и на ассемблере .

Фрагмент стиля программирования, который я использую, который может либо вызвать у вас энтузиазм, либо отвернуться в ужасе:

// GPIO part of a HAL for atsam3xa
enum class _port { a = 0x400E0E00U, . . . };

template< _port P, uint32_t pin >
struct _pin_in_out_base : _pin_in_out_root {

   static void direction_set_direct( pin_direction d ){
      ( ( d == pin_direction::input )
         ? ((Pio*)P)->PIO_ODR : ((Pio*)P)->PIO_OER )  = ( 0x1U << pin );
   }

   static void set_direct( bool v ){
      ( v ? ((Pio*)P)->PIO_SODR : ((Pio*)P)->PIO_CODR )  = ( 0x1U << pin );    
   }
};

// a general GPIO needs some boilerplate functionality
template< _port P, uint32_t pin >
using _pin_in_out = _box_creator< _pin_in_out_base< P, pin > >;

// an Arduino Due has an on-board led, and (suppose) it is active low
using _led = _pin_in_out< _port::b, 27 >;
using led  = invert< pin_out< _led > >;

На самом деле есть еще несколько слоев абстракции. Тем не менее, окончательное использование светодиода, скажем, для его включения, не показывает сложности или деталей цели (для Arduino Uno или синей таблетки ST32 код будет идентичным).

target::led::init();
target::led::set( 1 );

Компилятор не боится всех этих уровней, и поскольку виртуальные функции не задействованы, оптимизатор видит все (некоторые детали опущены, например, включение периферийных часов):

 mov.w  r2, #134217728  ; 0x8000000
 ldr    r3, [pc, #24]   
 str    r2, [r3, #16]
 str    r2, [r3, #48]   

Именно так я бы написал это на ассемблере - ЕСЛИ бы я понял, что регистры PIO могут использоваться со смещениями от общей базы. В данном случае я бы, наверное, так и сделал, но компилятор гораздо лучше меня оптимизирует такие вещи.

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

IMO, одна из ошибок встроенной разработки - сначала выбрать чип (на этом форуме часто задают вопрос: какой чип мне выбрать для .... В целом лучший ответ: это не имеет значения.)

(редактировать - ответ на «Так что с точки зрения производительности C или C++ будут на одном уровне?»)

Для одних и тех же конструкций C и C++ одинаковы. C++ имеет гораздо больше конструкций для абстракции (всего несколько: классы, шаблоны, constexpr), которые, как и любой инструмент, можно использовать как во благо, так и во вред. Чтобы дискуссии были более интересными: не все согласны, что хорошо, а что плохо...

Итак, с точки зрения производительности C или C++ будут на одном уровне? Я думаю, что у С++ будет больше перегрузки. Определенно, вы указали мне правильное направление, C ++ - это путь, а не C.
Шаблоны C++ навязывают полиморфизм времени компиляции, что может быть нулевой (или даже отрицательной) стоимостью с точки зрения производительности, поскольку код компилируется для каждого конкретного варианта использования. Однако это, как правило, лучше всего подходит для нацеливания на скорость (O3 для GCC). Полиморфизм во время выполнения, как и виртуальные функции, может понести гораздо большие потери, хотя, возможно, его легче поддерживать, а в некоторых случаях он достаточно хорош.
Вы утверждаете, что C++ лучше, но затем используете приведения типов в стиле C. Для стыда.
@JAB Мне никогда не нравились актеры в новом стиле, но я попробую. Но мой текущий приоритет — другие части этой библиотеки. Настоящая проблема, конечно, в том, что я не мог передать указатели в качестве параметров шаблона.
@Hans Мой стиль cto (Compile Time Objects) имеет довольно узкий вариант использования (близкий к оборудованию, известная ситуация во время компиляции), это скорее C-killer, чем замена традиционного использования виртуального OO. Полезным приколом является то, что отсутствие косвенности позволяет вычислить размер стека.

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

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

Есть несколько соображений:

  • Одни архитектуры — фон Неймана (ARM, MIPS), другие — гарвардские. Основные ограничения возникают, когда вашей программе C необходимо читать данные из ПЗУ, например, для печати строк, определения данных как "const" или подобных.

Некоторые платформы/компиляторы могут скрыть это «ограничение» лучше, чем другие. Например, на AVR вам нужно использовать определенные макросы для чтения данных ПЗУ. Для PIC24/dsPIC также доступны специальные инструкции tblrd. Однако, кроме того, в некоторых частях также доступна функция «видимости программного пространства» (PSVPAG) , которая позволяет отображать страницу FLASH в RAM, делая доступной немедленную адресацию данных без tblrd. Компилятор может сделать это достаточно эффективно.

ARM и MIPS являются фон-неймановскими, поэтому области памяти для ПЗУ, ОЗУ и периферийных устройств упакованы на 1 шину. Вы не заметите никакой разницы между чтением данных из ОЗУ или «ПЗУ».

  • Если вы погрузитесь ниже C и посмотрите на сгенерированные инструкции для определенных операций, вы обнаружите большие различия в вводе-выводе. ARM и MIPS представляют собой RISC -архитектуру регистров загрузки-хранения . Это означает, что доступ к данным на шине памяти должен осуществляться с помощью инструкций MOV. Это также означает, что любая модификация периферийного значения приведет к операции чтения-изменения-записи (RMW). Есть некоторые части ARM, которые поддерживают Bit-Banding, которые сопоставляют регистры set/clr-bit в периферийном пространстве ввода-вывода. Однако вам нужно закодировать этот доступ самостоятельно.

С другой стороны, PIC24 позволяет операциям ALU считывать и записывать данные напрямую через косвенную адресацию (даже с модификациями указателя...). Это имеет некоторые характеристики архитектуры, подобной CISC, поэтому 1 инструкция может выполнить больше работы. Этот дизайн может привести к более сложным ядрам ЦП, более низким тактовым частотам, более высокому энергопотреблению и т. д. К счастью для вас, эта часть уже разработана. ;-)

Эти различия могут означать, что PIC24 может быть «более быстрым» в отношении операций ввода-вывода, чем чип ARM или MIPS с аналогичной тактовой частотой. Тем не менее, вы можете получить компонент ARM/MIPS с гораздо более высокой тактовой частотой по той же цене/комплекту/конструктивным ограничениям. Я думаю, с практической точки зрения, я думаю, что много «изучения платформы» связано с тем, что архитектура может и не может делать, насколько быстрыми будут несколько наборов операций и т. д.

  • Периферийные устройства, управление часами и т. д. различаются в зависимости от семейства деталей. Строго говоря, это также изменится в экосистеме ARM между поставщиками, за исключением нескольких периферийных устройств, связанных с Cortex m, таких как NVIC и SysTick.

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

Кроме того, если вы покидаете экосистемы Microchip/бывшей Atmel, вы можете обнаружить, что части ARM требуют дополнительной настройки для их запуска. Я имею в виду; включить часы для периферийных устройств, затем настроить периферийные устройства и «включить» их, настроить NVIC отдельно и т. д. Это только часть кривой обучения. Как только вы не забудете сделать все эти вещи в правильном порядке, написание драйверов устройств для всех этих микроконтроллеров в какой-то момент будет казаться очень похожим.

  • Кроме того, попробуйте использовать такие библиотеки, как stdint.h, stdbool.h и т. д., если вы еще этого не сделали. Эти целые типы делают ширину явной, что делает поведение кода наиболее предсказуемым между платформами. Это может означать использование 32-битных целых чисел на 8-битном AVR; но если ваш код нуждается в этом, пусть будет так.

И да и нет. С точки зрения программиста вы идеально скрываете детали набора инструкций. Но это в какой-то степени уже не имеет значения, поскольку периферийные устройства, в которых и заключается весь смысл написания программы, не являются частью набора инструкций. В то же время вы не можете просто сравнивать части флэш-памяти размером 4096 байт в этих наборах инструкций, особенно при использовании C, объем потребления флэш-памяти в значительной степени определяется набором инструкций и компилятором, некоторые никогда не должны видеть компилятор (кашель PIC кашель) из-за того, сколько отходов этих ресурсов потребляется при компиляции. Другие потребляют флэш-память с меньшими накладными расходами. Производительность также является проблемой при использовании языка высокого уровня, а производительность имеет значение в приложениях MCU, поэтому может быть разница между расходом 3 долларов на плату для микроконтроллера или 1 долларом.

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

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