Arm7Tdmi-S (Armv4T) Против Cortex-M3 (Armv7-M)

Уже добрый десяток лет на рынке представлено множество микроконтроллеров на базе ядра ARM7TDMI. Это достаточно мощное ядро для однокристальных решений.

Он имеет разрядность 32 бита и рабочую частоту до 100 МГц; причём ядро однотактовое, т.е.

некоторые инструкции выполняются за 1 такт (в основном операции с регистрами, без обращения к внешним шинам процессора).

Ядро ARM7TDMI на голову превосходит все 8- и 16-битные чипы (AVR, MSC-51, PIC12/PIC16/PIC18/PIC24, MSP430 и т.д.) по вычислительным возможностям.

Однако сравнительно недавно ARM представила новое семейство ядер Cortex; нас будет интересовать его разновидность Cortex-M3, которая призвана именно заменить ARM7TDMI в нише однокристальных решений.

Мне посчастливилось работать с чипами NXP LPC1300, а точнее LPC1343, основанными на ядре Cortex-M3, сразу после их официального выпуска.

Сейчас им уже передана пара проектов.

И скажу вам, как бывалому ARM-программисту: они мне очень понравились, хотя у них есть свои особенности в архитектуре.

Итак, Cortex-M3 призван заменить ARM7TDMI. При его разработке компания ARM Ltd. ставила целью увеличить функциональность и добавить полезные инструкции без существенного усложнения логики схем процессора, тем самым увеличив плотность кода и производительность.

Из-за этого нам пришлось пойти на беспрецедентный шаг: ядро ARM впервые бинарно несовместимо с предыдущими семействами.

Собственно, это произошло по той причине, что Cortex-M3 не может выполнять 32-битный ARM-код. Все предыдущие ядра имели 2 режима работы и в каждом из них был свой набор команд. Эти режимы назывались ARM и Thumb. Первый работал с полным 32-битным набором команд, а второй — с упрощенным 16-битным набором команд. Фактически ядро всегда выполняло ARM-код, но в режиме Thumb был подключен некий декодер, который «на лету» «сопоставлял» 16-битные инструкции в их 32-битные аналоги.

Cortex-M3 отказался от 32-битного кода как класса.

В семействе Cortex есть еще несколько ядер (Cortex-M0, M1, A0-A3).

М3 расположен посередине.

М0, М1 еще более упрощены, а вот А-серия, наоборот, предназначена для тяжелых и высокопроизводительных приложений, и возможность выполнения ARM-кода там не убрали.

Массивность и низкая плотность кода — большая проблема ядер ARM; 32 бита на любую операцию дают о себе знать, плюс невозможно закодировать в инструкции константу больше 1 байта.

Именно из-за этого был введен дополнительный набор инструкций Thumb. Он обеспечивает большую плотность кода (средний выигрыш 20–30%), хотя и жертвует 5–10% производительности.

В Cortex была развита идея Thumb-кода.

Набор 16-битных команд Thumb был расширен, набор команд получил название Thumb-2. При компиляции в него падение производительности (по сравнению с чистым ARM-кодом) составляет всего несколько процентов, но экономия в объеме все та же 20-30%.

Особого внимания в наборе Thumb-2 заслуживают инструкции высокого уровня, такие как IT (дизайн с его использованием представлен ниже); в общем, система команд просто напичкана «фичами», призванными повысить оптимизацию при компиляции кода на языке C. Итак, конструкция на Thumb-2:

CMP r0, r1 ITE EQ ; if (r0 == r1) MOVEQ r0, r2 ; then r0 = r2; MOVNE r0, r3 ; else r0 = r3;

Нечто подобное можно сделать и в наборе инструкций ARM:

CMP r0, r1 ; if (r0 == r1) MOVEQ r0, r2 ; then r0 = r2; MOVNE r0, r3 ; else r0 = r3;

А вот в чистом Thumb придется немного «извратиться»:

CMP r0, r1 ; if (r0 == r1) BNE .

else MOV r0, r2 ; then r0 = r2; B .

endif .

else: MOV r0, r3 ; else r0 = r3; .

endif

Хотя если посчитать объёмы, то получим, что в случае с Thumb конструкция будет занимать 2*5=10 байт, на Thumb-2 объём будет 2*4=8 байт, на ARM целых 4*3= 12 байт (хотя в нем всего 3 инструкции).

Однако эта хваленая ИТ-инструкция компилятору Keil RealView MDK, видимо, неизвестна, так как при изучении сгенерированных листингов она не обнаружена, а визуально вывод ассемблерного кода компилятором все же больше похож на обычный Thumb. Либо сам исходный код специфичен, либо компилятор еще не «доработан» под новое ядро и систему команд. К сожалению, у меня нет информации о других компиляторах, хотя было бы неплохо посмотреть, что генерирует GCC. А вообще рекламируется просто сумасшедшая оптимизация кода, якобы конечный размер будет на 30-50% меньше, чем у того же исходника, скомпилированного под 8 и даже 16-битный микроконтроллер (например, в документе, представленном по первой ссылке по адресу конец статьи).

Скажу сразу: это несколько подтасованный результат, он справедлив только для 32-битного кода, т.е.

C-кода с обилием операций с переменными int и long, а также большим количеством вычислений (например, знаменитый тест Драйстоуна хорошо соответствует этим требованиям).

Если перенести ранее написанный и оптимизированный под 8 бит код, то при переносе на 32-битный процессор произойдет, наоборот, увеличение размера двоичного кода; по моему опыту код увеличивается в размерах почти в 1,5-2 раза.

Еще одним важным нововведением в Cortex-M3 стало добавление команды дивизии.

В ядрах ARM с древних времен предусмотрены операции умножения (с 64-битным результатом) и умножения с накоплением (также 64-битного результата).

Теперь к ним добавилась инструкция деления.

Конечно, она, скорее всего, потребляет много тактов, однако все равно намного быстрее, чем отдельная подпрограмма.

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

В отличие от ARM7TDMI, Cortex имеет гарвардскую архитектуру памяти (отдельные шины команд и данных).

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

Здесь (собственно так было во всех ARM после ARMv4, таких как ARM9, ARM11) при программировании отдельные шины не ощущаются; внутри чипа они по-прежнему объединены в единое адресное пространство.

Все чипы ARM имеют 32-битное линейное адресное пространство размером 4 ГБ (для x86-программистов это соответствует модели плоской памяти), которое содержит все адреса периферийных устройств, ПЗУ и ОЗУ.

Примечание(1): Несмотря на все преимущества, именно огромное адресное пространство является существенной проблемой при оптимизации кода: у нас 32-битная адресация, в инструкциях ARM/Thumb и даже Thumb-2 невозможно напрямую закодировать полный адрес определенного объекта.

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

Это также отрицательно влияет на объем кода.

Например, в MSC-51 для чтения переменной из ОЗУ может хватить 2 байта, но в ARM придется хранить как минимум 2 байта самой инструкции и 4 байта будут использоваться непосредственно для хранения адреса.

Заметка 2): Мне всегда хотелось попробовать поместить код (например, инструкцию возврата) в регистр периферии и передать ему управление, наблюдая за реакцией ядра.

На ARM7TDMI этот трюк может сработать из-за фон-неймановской организации памяти, но Кортекс со своим Гарвардом почти наверняка отправит его за тридевять земель, попав в один из абортов.

Следующее существенное отличие: один стек.

Если в ARM7TDMI для разных режимов ядра были выделены отдельные стеки (речь идет не о ARM/Thumb, а о режимах, в которые процессор переключается при входе в прерывания и при обработке исключений), то здесь стек только один.

Не знаю, как к этому относиться, в теории это менее гибко, но на практике чертовски удобно.

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

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

Вторым изменением, позволившим ускорить обработку прерываний, стал отказ от VIC. Да, монстра под названием VIC (Vector Interrupt Controller) больше нет. Да, это снова шаг от гибкости к простоте, но в микроконтроллерной системе случай, когда нужно на лету переназначать обработчики прерываний, встречается редко; для этого проще написать свой велосипед, чем возиться с настройкой ВИКа в каждом проекте.

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

Вместо VIC у нас теперь NVIC и куча векторов прерываний в начале FLASH. Если в ARM7TDMI векторы прерываний вначале занимали 32 байта, то здесь под прерывания от различных устройств выделяется несколько сотен байт. Более того, теперь это не инструкции перехода, а настоящие векторы с адресами.

Те.

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

Но главный сюрприз — это первые 2 вектора прерывания.

Думаете Ресет и что-то еще? НЕТ! По адресу 0 лежит. значение стека, оно вводится ядром в регистр стека аппаратно при сбросе.

А по смещению 4 — адрес точки входа.

Что это дает? А вот что: мы можем сразу приступить к выполнению программы из Си-кода без предварительной инициализации.

Конечно, в этом случае вам придется вручную копировать раздел RW в ОЗУ и сбрасывать ZI (если вы полностью откажетесь от помощи компилятора).

Эта четкая С-ориентация заметна и на примерах проектов для Cortex. Все инициализации перенесены с ассемблера на C. Из-за исключения многих стеков стало ненужно инициализировать их в самом начале.

В то же время другие инициализации перешли в код C. Еще одно интересное отличие - в системе команд: добавлены высокоуровневые инструкции WFI (ожидание прерывания), WFE (ожидание события) и другие, упрощающие создание многопоточных приложений и операционных систем.

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

Примечание: Хотя многоядерные микроконтроллеры существуют в виде того же Parallax Propeller (он имеет целых 8 32-битных ядер), его нельзя назвать полноценным и пригодным для коммерческого использования (а не для любительских поделок).

Также в описание ядра Cortex-M3 добавлен 1 таймер.

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

Примечание: Таймер в описании ядра — очень полезная и важная вещь.

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

Это очень полезно для совместимости кода: не нужно писать модули поддержки кучи реализаций таймеров от разных производителей (как в случае с ARM7TDMI).

Однако каждый производитель все равно реализует дополнительные таймеры по-своему, но даже один стандартный — хороший шаг к универсальности.

В заключение стоит сказать, что в документации ядра описан и модуль MPU (Memory Protection Unit).

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

Однако этот модуль является необязательным и производители чипов не спешат его интегрировать.

Его нет даже у старшего семейства NXP LPC1700. У других производителей подобного не замечено.

Тем не менее, защита памяти, не говоря уже о виртуальной памяти, по-прежнему остается уделом дорогих и больших монстров.

Ссылки по теме:

Теги: #микроконтроллеры #arm #архитектура #cortex-m3 #Чулан #arm7tdmi
Вместе с данным постом часто просматривают: