СОДЕРЖАНИЕ
ВВЕДЕНИЕ
1. ПРОГРАММНАЯ АРХИТЕКТУРА МИКРОКОНТРОЛЛЕРОВ
С ЯДРОМ ARM7
1.1. Особенности выполнения инструкций микроконтроллеров ARM.......... 11
1.2. Основы аппаратной архитектуры микроконтроллеров ARM
1.3. Программное обеспечение для систем с ARM микроконтроллерами..... 15
2. ИНСТРУМЕНТЫ ПРОГРАММИРОВАНИЯ МИКРОКОНТРОЛЛЕРОВ
ARM
2.1. Среда разработки Keil C и интерфейс пользователя Vision IDE............. 18 2.2. Программа “Hello, World!” в среде Keil
3. ПРОГРАММИРОВАНИЕ ПЕРИФЕРИЙНЫХ УСТРОЙСТВ
МИКРОКОНТРОЛЛЕРОВ ARM НА KEIL C
4. ПРОГРАММНЫЙ ИНТЕРФЕЙС С/C++ И АССЕМБЛЕРА
ДЛЯ МИКРОКОНТРОЛЛЕРОВ ARM4.1. Базовые примеры программного кода на языке ассемблера
4.2. Примеры решения практических задач программирования на языке ассемблера
4.3. Использование встроенного ассемблера языка С++ в приложениях Keil
5. ОТЛАДКА ПРОГРАММНОГО КОДА МИКРОКОНТРОЛЛЕРОВ ARM...... 5.1. Компиляция исходных текстов программы
5.2. Компоновка объектных модулей и генерация исполняемого файла программы
5.3. Основы отладки приложений в среде Keil
5.4. Методика пошаговой отладки приложения и анализ программного кода
Содержание
6. АНАЛИЗ И ОПТИМИЗАЦИЯ ПРОГРАММНОГО КОДА
МИКРОКОНТРОЛЛЕРОВ ARM6.1. Выбор типов данных в приложении
6.2. Использование указателей для оптимизации ARM приложений........... 6.3. Оптимизация циклов
6.4. Оптимизация приложений с помощью языка ассемблера
6.5. Применение инструкций условного выполнения для оптимизации программных алгоритмов
ЗАКЛЮЧЕНИЕ
ВВЕДЕНИЕ
Сегодняшние достижения в области электроники и компьютерных технологий открывают все новые и новые перспективы для создания и применения устройств, спроектированных на микроконтроллерах и микропроцессорах, в самых различных сферах человеческой деятельности. Одним из наиболее популярных микропроцессорных платформ в настоящее время является ARM. За последние 10 лет архитектура ARM развивалась особенно интенсивно, опережая все остальные процессорные технологии. В настоящее время 32-разрядные микроконтроллеры ARM используются в наиболее быстро развивающихся сегментах рынка, ориентированных на мобильные устройства и устройства коммуникации. Ежегодно рынок ARM микроконтроллеров захватывает все новые и новые типы устройств и технологических решений, а количество выпускаемых кристаллов ARM исчисляется десятками миллионов.На сегодняшний день ARM микроконтроллеры применяются практически везде, начиная с мобильных телефонов и заканчивая устройствами электронного управления автомобилями и GPS навигаторами. Более того, ARM микропроцессоры вторгаются в отрасли, ранее считавшиеся недоступными для данной технологии – это настольные и переносные компьютеры и серверные решения. Так, например, уже следующее поколение операционных систем Windows, анонсированное как Windows 8, наряду с процессорами Intel и AMD будет работать и на платформе ARM.
Эта книга посвящена анализу практических методов программирования систем с микроконтроллерами ARM на языке С/С++. Разработка программной части таких систем занимает львиную долю времени в процессе проектирования, причем в этот процесс вовлечена масса программистов, для которых эта книга и предназначена в первую очередь. Помимо анализа элементов чистого программирования на С/С++ в данной книге рассматривается и другой, не менее важный аспект разработки программного обеспечения – отладка и оптимизация программного обеспечения для микропроцессоров ARM.
Производительность приложений, написанных на языке высокого уровня, даже на таком мощном, как С++, будет существенно зависеть от того, насколько хорошо разработчик понимает программную архитектуру ARM и владеет основами программирования на нижнем уровне. По этой причине значительная Введение часть материала книги посвящена анализу программных интерфейсов языков С/С++ и ассемблера, что позволяет достичь высокой производительности С/ С++ приложений.
Важнейшим этапом разработки программного обеспечения является отладка программного кода приложения. Если для самых простых приложений процесс отладки может занять несколько минут, то комплексные приложения могут потребовать от инженера-разработчика значительных усилий и времени, сопоставимого с временем разработки исходных текстов. Для успешной отладки приложения, написанного на языке высокого уровня, программист должен знать основы язык ассемблера микроконтроллеров ARM – в этой книге данная тема рассматривается достаточно широко, так же как и основные методы анализа ошибок и оптимизации программного кода.
Материал книги имеет практическую направленность, поэтому читатели найдут здесь много примеров исходных текстов программ, иллюстрирующих теоретические аспекты тех или иных проблем. Книга будет полезна разработчикам программного обеспечения для микропроцессоров ARM, инженерам и всем желающим ознакомиться с принципами программирования и отладки программного обеспечения для данной архитектуры. Книга содержит несколько глав, краткое описание каждой из которых приводится далее.
• Глава 1. Программная архитектура микроконтроллеров с ядром ARM7.
В данной главе рассматривается программная архитектура микроконтроллеров ARM и специфика разработки приложений для RISC-процессоров.
Часть материала главы посвящена особенностям аппаратной реализации микроконтроллеров ARM и взаимодействия различных функциональных узлов на кристалле микропроцессора. Рассматривается структура программного обеспечения для систем с микроконтроллерами ARM, а также варианты выбора той или иной модели проектирования для реализации программной части.
• Глава 2. Инструменты программирования микроконтроллеров ARM. В этой главе дается общая оценка инструментальным средствам программирования систем на базе микроконтроллеров ARM, а также рассматривается один из наиболее популярных пакетов разработки приложений в среде С/С++ для ARM – компилятор Keil С. Значительная часть материала главы посвящена методам создания приложений, положенных в основу инструментального пакета Keil C для ARM. На примере простейшего приложения рассматриваются все этапы разработки пользовательского программного обеспечения, включая компиляцию исходных текстов, генерацию объектных модулей и сборку приложения.
• Глава 3. Программирование периферийных устройств микроконтроллеров ARM на Keil C. В этой главе рассматриваются принципы функционирования периферийных устройств (таймеров, портов ввода-вывода, аналого-цифровых и цифро-аналоговых преобразователей), входящих в состав кристаллов современных микроконтроллеров ARM и практические методы программирования таких устройств. На практических примерах рассмотВведение рено функционирование системы прерываний микроконтроллеров ARM.
Все исходные тексты программ сопровождаются детальным описанием.
• Глава 4. Программный интерфейс С/C++ и ассемблера для микроконтроллеров ARM. Материал главы посвящен реализации программного интерфейса между программным кодом, написанным на языке С/С++ и процедурами, разработанными на языке макроассемблера среды Keil.
Рассматриваются соглашения о вызовах процедур и передаче параметров процедурам, доступ к общим данным и функциям, использование стека и т.д. Материал главы сопровождается многочисленными примерами программ, иллюстрирующими различные подходы при реализации интерфейса между фрагментами кода на С++ и ассемблере.
• Глава 5. Отладка программного кода микроконтроллеров ARM. Данная глава посвящена анализу основных методов отладки программного кода и методике обнаружения часто встречающихся ошибок при программировании микроконтроллеров ARM. Часть материала главы посвящена основам дизассемблирования и анализа программного кода.
• Глава 6. Анализ и оптимизация программного кода микроконтроллеров ARM. В этой главе дается подробный анализ методов оптимизации приложений на уровне исходных текстов программ. Рассматриваются такие методы оптимизации кода, как разворачивание циклов, применение инструкций условного выполнения, выбор типов переменных и т.д. Теоретический материал сопровождается многочисленными примерами исходных текстов программ.
ПРОГРАММНАЯ АРХИТЕКТУРА
МИКРОКОНТРОЛЛЕРОВ С ЯДРОМ ARM
В этой главе будут рассмотрены основы архитектуры ядра ARM7TDMI, которое является базовым модулем для многочисленной линейки процессоров ARM.Ядро ARM базируется на архитектуре RISC (Reduced Instruction Set Computing, архитектура процессоров, построенная на основе сокращенного набора команд), особенностью которой является использование простых и эффективных инструкций процессора, которые могут выполняться за один цикл. Базовые концепции RISC предполагают перенос основного акцента в разработке приложений с аппаратной части на программную, поскольку производительность приложений поднять значительно проще программными методами, чем используя сложные аппаратные решения.
Вследствие этого программирование RISC-процессоров выдвигает более существенные требования к эффективности компиляторов, по сравнению с архитектурой CISC (Complete Instruction Set Computing, архитектура процессоров с широким набором различных машинных команд переменной длины и разным временем их исполнения). Микропроцессоры с CISC архитектурой (например, Intel x86) не столь требовательны к программным средствам разработки – здесь основной упор делается на производительность аппаратной части. На Рис. 1. показаны эти отличия.
Глава 1. Программная архитектура микроконтроллеров с ядром ARM Если рассматривать архитектуру RISC более детально, то в ее основе лежат следующие базовые принципы, перечисленные далее.
1. Система инструкций (команд) процессора. Стандартный RISC-процессор имеет ограниченный набор типов инструкций, причем каждая из этих инструкций выполняется за один машинный цикл процессора. Разработка отдельных программных алгоритмов, например, деления, целиком возлагается на инструментальное средство разработки программ (компилятор) или самого разработчика. Каждая инструкция процессора имеет фиксированную длину, что позволяет успешно использовать принцип конвейера для выборки следующей инструкции в то время, пока предыдущая находится на стадии декодирования. В противоположность этому, в CISCпроцессорах инструкции имеют различную длину и могут выполняться за несколько машинных циклов.
Для иллюстрации этой концепции посмотрим, например, как выполняются операции сложения двух 32-разрядных целых чисел в процессорах CISC и RISC. Если, например, требуется сложить две переменные i1 и i2, находящиеся в оперативной памяти, и сохранить результат в переменной res, также находящейся в памяти, то для Intel x86 процессора потребуются следующие команды:
В эти вычисления вовлечен один регистр общего назначения EAX, а вычисление суммы (инструкция ADD) выполняется над операндами, расположенными в регистре и в памяти.
Для RISC-процессора, например, ARM7 LPC2148, та же операция потребует следующей последовательности инструкций:
Для выполнения любой операции в RISC-процессоре (кроме загрузки и сохранения данных, LDR и STR соответственно) оба операнда должны находиться в регистрах – это видно из мнемоники инструкции ADD, где переменные i1 и i2 находятся в регистрах r0 r1 соответственно.
Сравнивая два фрагмента кода для CISC и RISC процессоров, мы видим, что для RISC-процессора требуется больше инструкций. С другой стороны, большое количество регистров в ARM процессорах позволяет очень эффективно выполнять вычислительные операции с несколькими переменными, поскольку промежуточные результаты вычислений можно разместить в регистрах. Кроме того, в ARM процессорах можно использовать инструкции множественного доступа к нескольким ячейкам памяти, что повышает производительность операций чтения/записи данных.
Глава 1. Программная архитектура микроконтроллеров с ядром ARM Дополнительные возможности повышения производительности ARM микропроцессоров достигаются и при использовании инструкций условного выполнения, когда следующая инструкция выполняется только в том случае, если предыдущая инструкция установила определенные флаги в регистре состояния программы. Эти и другие возможности оптимизации быстродействия программного кода ARM мы рассмотрим далее в этой книге.
2. Конвейер инструкций. Процесс обработки каждой инструкции процессора разбивается на несколько этапов, которые выполняются одновременно.
В идеальном варианте, для достижения максимальной производительности конвейер инструкций должен продвигаться на один шаг в каждом машинном цикле, при этом декодирование команды может осуществляться на одном шаге конвейера. Этот подход отличается от принятого для CISCархитектур, в которых декодирование инструкций требует выполнения специальных микропрограмм.
3. Использование регистров процессора. В RISC-процессорах имеется набор многочисленных регистров общего применения. Каждый из регистров может содержать данные или адрес данных в памяти, поэтому регистры являются локальными хранилищами данных при выполнении всех оперций в процессоре. Для сравнения: в CISC-процессорах имеется ограниченный набор регистров, каждый из которых имеет отдельное функциональное назначение, поэтому многие инструкции CISC в качестве одного из операндов используют ячейку памяти. В примере сложения двух чисел для CISC процессоров Intel x86 использовалась, например, инструкция ADD, одним из операндов которой являлясь ячейка памяти. Такая инструкция требует достаточно много машинных циклов, что снижает производительность приложения, особенно если подобные инструкции используются при циклических вычислениях. Несмотря на существенные различия в архитектурах, в последнее время осуществляется постепенное сближение архитектур RISC и CISC. Так, например, CISC-микропроцессоры на уровне микропрограмм реализованы по принципам RISC, что увеличивает скорость выполнения микроинструкций.
Здесь нужно отметить еще один важный момент: базовое ядро ARM микроконтроллеров не вполне наследует концепции RISC по той причине, что специфика приложений для встроенных и мобильных систем требует большей гибкости и производительности, чем может обеспечить чистая RISC-архитектура. Кроме того, одним из основных требований к ARM-процессорам является низкое энергопотребление, поскольку мобильные и переносные устройства работают, как правило, с батарейным питанием.
Программная архитектура современных ARM-микроконтроллеров отличается от той, что реализована в традиционных RISC-устройствах, прежде всего в плане более расширенных возможностей инструкций процессора. Эти отличия обеспечивают более высокую производительность встроенных приложений – они описаны в следующем разделе.
1.1. Особенности выполнения инструкций микроконтроллеров ARM 1.1. Особенности выполнения инструкций микроконтроллеров ARM Некоторые инструкции ARM требуют для выполнения не одного, как классические RISC команды, а нескольких машинных циклов. Так, например, инструкции множественной загрузки/сохранения (load–store–multiple instructions), в зависимости от количества задействованых регистров, используют разное количество машинных циклов. Обмен данными с памятью для таких инструкций осуществляется по последовательным адресам, что повышает производительность системы по сравнению с операциями с произвольным доступом.
Характерной особенностью ARM-процессоров является наличие встроенной многорегистровой схемы циклического сдвига (barrel shifter), которая позволяет выполнять сложные арифметические и логические операции в одной инструкции. Циклический сдвигатель реализован как аппаратное устройство на кристалле процессора и позволяет выполнять арифметические и логические сдвиги содержимого операнда-источника перед выполнением инструкции. С помощью схемы сдвига можно реализовать операции быстрого умножения на степень двух, а также комбинированные операции сложения/вычитания/умножение целых чисел.
Все инструкции процессора ARM могут выполняться в так называемом условном режиме (conditional execution). Это означает, что инструкция будет выполнена только в том случае, если условие, указанное в мнемонике инструкции, истинно. Например, команда сложения содержимого двух регистров r0 и r1 и результатом в регистре r0 c безусловным выполнением имеет мнемонику Если эта же команда должна выполняться, только если предыдущие инструкции установили флаг Z регистре состояния, то мнемоника инструкции сложения будет следующей:
Инструкция будет пропущена, если требование Z=1 выполняться не будет.
Условно выполняемые инструкции позволяют оптимизировать программный код за счет минимизации количества ветвлений, что очень важно при разработке приложений реального времени с ARM-процессорами.
В набор инструкций современных микропроцессоров ARM включена также группа инструкций, позволяющая программировать алгоритмы цифровой обработки сигналов. К этой группе относятся инструкции быстрого умножения 16 ґ 16 бит, а также инструкции умножения с насыщением. С помощью этой группы инструкций можно создавать эффективные программные алгоритмы без использования специального процессора цифровой обработки сигналов.
Глава 1. Программная архитектура микроконтроллеров с ядром ARM 1.2. Основы аппаратной архитектуры микроконтроллеров ARM Встроенные и мобильные устройства и системы широко используются для управления и измерения в самых различных отраслях науки, техники и производства.
ARM микропроцессоры как нельзя лучше подходят для реализации таких систем. Кроме ядра, выполняющего функции вычислительного интеллектуального модуля, на кристалле ARM микроконтроллера имеется ряд устройств, которые позволяют строить интерфейс с внешним миром (датчиками, реле и двигателями, компьютерами и т. д.).
Ядро ARM, помимо реализации вычислительных функций, выполняет функции управления периферийными устройствами кристалла, такими, как порты ввода-вывода, таймеры, устройства оцифровки сигналов (аналого-цифровые и цифро-аналоговые преобразователи). Аппаратную часть современного ARM микропроцессора можно представить так, как показано на рис. 1.2.
1.2. Основы аппаратной архитектуры микроконтроллеров ARM Рассмотрим эту схему детально, поскольку она отображает основные функциональные узлы и связи между ними, характерные для всех микроконтроллеров ARM. Важнейшим узлом в архитектуре микроконтроллера является контроллер прерываний, который осуществляет захват и приоритезацию внешних сигналов (событий), поступающих от периферийных устройств. Именно наличие этого функционального блока позволяет создавать системы реального времени, хотя существуют и другие подходы к построению таких систем.
Когда какое-либо устройство требует вмешательства со стороны процессора, оно выставляет сигнал прерывания. Контроллер прерывания, в свою очередь, управляет доступом к источнику прерывания со стороны программного обеспечения посредством установки соответствующих бит в регистрах прерываний.
В микроконтроллерах ARM применяются два типа контроллеров прерываний – стандартный и векторизованный (Vector Interrupt Controller, VIC).
Стандартный контроллер прерывания просто посылает сигнал прерывания микроконтроллеру, когда какое-либо устройство требует немедленного обслуживания, и устанавливает сигнал прерывания на соответствующей линии.
Такой контроллер можно запрограммировать так, чтобы он игнорировал или маскировал запросы на обслуживание от какого-либо устройства или целой группы устройств. Программа-обработчик прерывания определяет, какое устройство выставило запрос на обслуживание, посредством чтения регистра карты прерываний в контроллере прерываний.
Векторизованный контроллер прерываний выполняет более гибкую обработку запроса на обслуживание. Такой контроллер позволяет задавать приоритеты прерываний и упрощает процесс определения устройства, которое вызвало прерывание. После определения источника прерывания и его приоритета VIC-контроллер выставляет сигнал прерывания для микроконтроллера только в том случае, если приоритет только-что инициированного запроса выше, чем у прерывания, которое обслуживается в данный момент. В зависимости от способа реализации, векторизованный контроллер прерываний может либо вызвать стандартный обработчик прерывания, который, в свою очередь, может использовать адрес обработчика прерывания из VIC-контроллера, либо передать обработку прерывания микроконтроллеру. Во втором случае микроконтроллер вызывает обработчик прерывания напрямую.
Важнейшей функцией микроконтроллера является управление ресурсами памяти системы. Современные устройства ARM включают блоки управления памятью, архитектура которых может отличаться в зависимости функций, заложенных в устройство фирмами-производителями, однако принципы их построения одинаковы. В большинстве современных микроконтроллеров для хранения программного кода используется флэш-память, а данные хранятся в статической памяти.
Координация доступа к регионам памяти, занимаемых программным кодом и данными на аппаратном уровне, как раз и осуществляется посредством блока управления памятью. На уровне программы пользователя управление ресурсами памяти осуществляется посредством стандартных библиотечных функций Глава 1. Программная архитектура микроконтроллеров с ядром ARM C (malloc, realloc, free и т. д.), которые оперируют с регионами памяти, находящимися в куче (heap). Эффективность использования и распределения памяти для работающего приложения существенно влияет на производительность системы, поэтому все современные компиляторы (Keil, IAR и др.) имеют целый ряд опций для настройки распределения ресурсов памяти.
Для управления периферийными устройствами и контроллером памяти в микроконтроллерах ARM реализована шинная архитектура. Эта архитектура кардинально отличается от той, что используется, например, в персональных компьютерах на базе процессоров x86 (Intel). В персональных компьютерах все внешние устройства подсоединяются к процессору через одну их шин PCI и/или PCI-Express, которые являются внешними по отношению к процессору.
В противоположность этому, все шины микроконтроллеров ARM реализованы на кристалле процессора.
Все устройства, присоединеные к внутренней шине микроконтроллера ARM, могут работать в одном из двух режимов: ведущего (master) или ведомого (slave).
Сам ARM микроконтроллер всегда работает в режиме ведущего – это означает, что он инициирует запросы на обмен данными для устройств, находящихся на шине. Периферийные устройства могут работать только в режиме ведомого и осуществлять обмен данными по запросу ведущего.
Архитектуру шины микроконтроллера можно представить в виде двух уровневой модели. Первый уровень реализует непосредственные физические соединения посредством электрических сигналов и характеризуется разрядностью шины, которая может варьироваться от 16 до 64. Второй уровень характеризуется тем, что здесь используются определенные протоколы – система логических правил, в соответствии с которыми выполняется обмен данными между процессором и периферийными устройствами.
В микроконтроллерах ARM применяется архитектура шин под названием AMBA (Advanced Microcontroller Bus Architecture), разработанная около двадцати лет тому назад. Эта архитектура была адартирована для применения с ARM микроконтроллерами. Первыми такими адаптированными шинами были ASB (ARM System Bus) и APB (ARM Peripheral Bus). Позже была разработана шина AHB (ARM High Performance Bus), которая в настоящее время применяется в большинстве микроконтроллеров.
Преимуществом использования шины AMBA является то, что разработчики периферийных устройств микроконтроллеров могут использовать одно и то же устройство во многих проектах без каких-либо изменений аппаратно-архитектурного решения. Если сравнивать различные модификации шин, то AHB способна обеспечить более высокую пропускную способность по сравнению с ASB. Это объясняется тем, что AHB является мультиплексированной шиной с централизованным управлением, в то время как ASB базируется на принципе двунаправленного потока данных. По этой причине шина AHB может функционировать при высоких тактовых частотах, а разрядность данных на этой шине может достигать 64 или 128 бит.
На Рис. 1.2 показана конфигурация кристалла ARM микроконтроллера с 1.3. Программное обеспечение для систем с ARM микроконтроллерами тремя шинами: AHB – для подсоединения высокопроизводительных периферийных устройств, APB – для работы с низкоскоростными устройствами и внешняя шина для подсоединения внешних устройств. Обратите внимание на то, что для устройств на внешней шине требуется мост для перехода на шину AHB.
1.3. Программное обеспечение для систем с ARM микроконтроллерами Программное обеспечение встроенных систем на микроконтроллерах в самом общем случае может состоять из четырех основных компонентов, показанных на Рис. 1.3. Каждый программный компонент в этом стеке использует определенный уровнь абстракции для разделения программного кода и аппаратного устройства, управляемого этим кодом. Код инициализации выполняется первым при сбросе или перезагрузке системы и определяется специфическими характеристиками данного типа процессора, архитектурой прерываний и системой управления памятью. Код инициализации обычно очень короткий, поскольку его основная функция – сконфигурировать базовые компоненты системы и передать управление загрузчику операционной системы.
Операционная система выстраивает необходимую инфраструктуру для управления приложениями и аппаратными ресурсами. Для многих встроенных и переносных систем вообще не требуется комплексная операционная система – в этих случаях можно обойтись каким-либо простым менеджером заданий.
Общая структура такого менеджера (Рис. 1.4), управляемого событиями, характерна для многих простых операционных систем реального времени (RTOS).