На что стоит променять Cortex-M3?

9b4cecde956d450a9b81dc0a0c502d61.pngARM Cortex-M3 — это, пожалуй, самое популярное на сегодняшний день 32-разрядное процессорное ядро для встраиваемых систем. Микроконтроллеры на его базе выпускают десятки производителей. Причина этому — универсальная, хорошо сбалансированная архитектура, а следствие — непрерывно растущая база готовых программных и аппаратных решений.

Ругать Cortex-M3, в общем-то, не за что, но сегодня я предлагаю подробно рассмотреть Cortex-M4F — расширенную версию всеми любимого процессорного ядра. Перенести проект с микроконтроллера на базе Cortex-M3 на кристалл на базе Cortex-M4F довольно просто, а для ряда задач такой переход стоит затраченных усилий.

Под катом краткий обзор современных Cortex’ов, обстоятельное описание блоков и команд, отличающих Cortex-M4F от Cortex-M3, а также сравнение процессорных ядер на реальной задаче — будем измерять частоту мерцания лампы на микроконтроллерах с разными ядрами.

Часть обзорная


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

Компания ARM Ltd. разрабатывает микропроцессорные и микроконтроллерные ядра с RISC-архитектурой и продает производителям электронных компонентов лицензии на производство кристаллов по соответствующей технологии. Таких производителей по всему миру десятки и даже сотни, есть среди них и отечественные компании.
Современные ядра ARM объединены названием Cortex.
Кстати говоря, слово «cortex» переводится как «кора головного мозга» — структура, отвечающая за согласованную работу органов, мышление, высшую нервную деятельность. По-моему, прекрасное название.

Итак, процессорные ядра ARM Cortex разделены на три основные группы:

  • Cortex-A — Application Processors — для приложений, требующих высокой производительности; чаще всего на них запускается linux, android и им подобные ОС
  • Cortex-R — Embedded Real-time Processors — для приложений реального времени
  • Cortex-M — Embedded Processors — для встраиваемых систем

Рассмотрим последнюю группу, постепенно приближаясь к паре Cortex-M3 / Cortex-M4F. Всего на конец 2015 года представлено шесть процессорных ядер: Cortex-M0, -M0+, -M1, -M3, -M4, -M7.
Из этого списка часто «выпадает» Cortex-M1, это оттого, что -M1 разработан и используется исключительно в приложениях связанных с FPGA. Остальные ядра не имеют столь специализированной области применения и отличаются по производительности — от самого простого -M0 до высокопроизводительного -M7.

baa68812100e44879c8537d4e06500b1.png

По сравнению с Cortex-M0, Cortex-M0+ дополнительно оснащен блоком защиты памяти MPU, буфером Micro Trace Buffer для отладки программ, а также имеет двухступенчатый конвейер вместо трехступенчатого и упрощенный доступ к периферийным блоками и линиям ввода/вывода.

Cortex-M0 и Cortex-M0+ имеют одношинную фон-неймановскую архитектуру, а ядро Cortex-M3 — уже гарвардскую. Cortex-M3 довольно сильно отличается от «младших» представителей линейки и имеет гораздо более широкие возможности.

Cortex-M4 построен по абсолютно той же архитектуре и «структурно» не отличается от Cortex-M3. Разница заключается в поддерживаемой системе команд, но об этом позже. Cortex-M4F отличается от -M4 наличием блока вычислений с плавающей точкой FPU.

Архитектура Cortex-M7 представлена относительно недавно и отличается от Cortex-M3/M4 так же сильно, как Cortex-M3/M4 отличаются от Cortex-M0. 6-ступенчатый суперскалярный конвейер, отдельная кэш-память для данных и команд, конфигурируемая память TCM и другие отличительные функции этого ядра «заточены» для достижения максимальной производительности. И действительно, возможности контроллеров на базе Cortex-M7 сравнивают скорее с Cortex-A5 и -R5, чем с другими контроллерами группы Embedded Processors. Границы применения технологий продолжают размываться.

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

85162008940b4d819a9a288dba29792c.png

Ядра Cortex-M0 и Cortex-M0+ имеют одну и ту же систему команд. Набор инструкций Cortex-M3 включает все команды Cortex-M0 и около сотни дополнительных инструкций. Процессорные ядра Cortex-M4 и Cortex-M7 имеют, опять же, идентичный набор команд — набора команд Cortex-M3 плюс так называемые DSP-инструкции. Ядро Cortex-M4F дополнительно к набору Cortex-M4 / -M7 поддерживает команды вычислений с плавающей точкой, а система команд Cortex-M7F включает ещё 14 команд для операций над числами с плавающей точкой двойной точности.

Часть теоретическая


Итак, ближайшими «соседями» популярного процессорного ядра Cortex-M3 являются Cortex-M4, дополненный поддержкой DSP-инструкций, и Cortex-M4F, дополнительно содержащий блок FPU и поддерживающий соответствующие команды. Рассмотрим DSP- и FPU-команды.

DSP-инструкции


Аббревиатура DSP чаще всего расшифровывается как Digital Signal Processor, т.е. отдельный и вполне самостоятельный контроллер или сопроцессор, предназначенный для задач цифровой обработки сигналов. Не стоит путать специализированную DSP-микросхему и набор DSP-инструкций. DSP-команды (расшифровывается Digital Signal Processing вместо Processor) — это набор команд, который поддерживается рядом процессорных ядрер ARM и соответствует некоторым типовым для цифровой обработки сигнала операциям.

Первая группа таких операций — это умножение с накоплением (Single-cycle Multiply Accumulate или просто MAC).
Для самых маленьких: умножение с накоплением описывается формулой S = S + A x B. Соответствующие команды описывают умножение двух регистров с суммированием результата в аккумулятор и смежные операции: умножение с вычитанием результата из аккумулятора, умножение без использования аккумулятора и т.д.

Операции предусмотрены для 16- и 32-разрядных переменных и играют важную роль во многих типовых алгоритмах цифровой обработки сигналов. Например, КИХ-фильтр (это классический, почти банальный «например») по сути представляет собой последовательность операций умножения с накоплением, а значит скорость его работы напрямую зависит от скорости выполнения умножения с накоплением.
Все MAC-инструкции в микроконтроллерах с ядром Cortex-M4(F) выполняются за один машинный цикл.

Вторая группа DSP-инструкций — это операции параллельной обработки данных (Single Instruction Multiple Data, SIMD), позволяющие оптимизировать обработку данных за счет параллелизма вычислений. Пары независимых переменных попарно помещаются в один регистр большей размерности, а арифметические операции проводятся уже над «большими» регистрами.
Например, команда SADD16 подразумевает одновременное сложение двух пар 16-разрядных знаковых чисел с записью результата в регистр, хранящий первый операнд.

SADD16 R1, R0


1754b5a272474a4b9f82eb170030dcc2.png

Поскольку регистры общего назначения имеют разрядность 32 бит, в каждый из них можно записать не только по две 16-разрядных переменных (полуслов), но и до четырех 8-разрядных переменных (байт). Несложно прикинуть зачем нужна команда SADD8.

Вот более сложная операция: умножение старших полуслов, умножение младших полуслов и суммирование произведений между собой и с 64-разрядным накоплением. Команда SMLALD описывает все эти действия и выполняется Cortex-M4 за один машинный цикл. SMLALD, как и многие другие команды, совмещает умножение с накоплением и обработку данных по принципу SIMD.

SMLALD      R6, R8, R5, R1


3c6e60dcae2e4cf8a553689ebf04995b.png

И простые SIMD-команды (знаковые и беззнаковые 8- и 16-разрядные сложение и вычитание и т.п.), и сложные команды, подобные SMLALD, выполняются за один машинный цикл.

Следующая группа DSP-инструкций — команды операций с насыщением (Saturating instructions). Они также известны как операции с отсечкой и представляют собой своеобразную защиту от переполнений. При использовании стандартных команд, регистр, хранящий результат, при переполнении «перезагружается» с нуля. Команды, предусматривающие насыщение, при переполнении фиксируют результат на допустимом разрядностью максимуме и с программиста снимается необходимость заботиться о флагах переполнения.

7d73d756f06c4080988ca8dd92e6be75.png

Среди команд процессорного ядра Cortex-M4 есть и «обычные» арифметические операции, и те же операции с насыщением. Использование последних особенно востребовано в задачах, где точностью вычислений можно пожертвовать ради скорости и таких в ЦОС немало.

FPU-инструкции


Аппаратная поддержка вычислений с плавающей запятой (или точкой, кому как больше нравится) — это особенность ядра Cortex-M4F и более старших представителей линейки Cortex-M.

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

(-1)s * m × be,
где s — знак, b-основание, e — порядок, а m — мантисса

Использование формата с плавающей точкой предпочтительно при обработке сигналов за счет гораздо более широкого диапазона значений переменных формата float. Использование операций FPU также избавляет разработчика от необходимости следить за разрядностью. Формат чисел с плавающей точкой одинарной точности описывается стандартом IEEE 754, это представление и используется в микроконтроллерах с ядром Cortex-M4F. Диапазон допустимых значений составляет (10–38… 1038) при приблизительном пересчете в десятичные числа.

87eea7c176b742a3a887d1e198976779.png

Для формата чисел с плавающей запятой двойной точности, как в Cortex-M7F, используется тот же принцип, но вместо 32-разрядного представления используется 64-разрядное, на порядок приходится 11 бит, а на мантиссу 52.

О том как и зачем используется формат с плавающей точкой не раз написано на хабре (вот , например, отличная статья). Мне, пожалуй, не написать лучше, поэтому идем дальше.

Список ассемблерных DSP- и FPU-команд


Чтобы немного прочувствовать масштаб и понять насколько может быть ускорена обработка данных с использованием Cortex-M4 можно поизучать полный перечень DSP- и FPU-инструкций. У меня есть большие сомнения на счет практической ценности этих таблиц, это хотя бы показательно. Все DSP- и большинство FPU-инструкций выполняются за один машинный цикл.

DSP-инструкции ядра Cortex-M4
Команда Операция
PKHTB,  PKHBT перезапись полуслова из одного регистра в другой, при необходимости сдвиг содержимого «принимающего» регистра
QADD знаковое сложение с насыщением 
QADD16 знаковое сложение соответствующих полуслов двух операндов (с насыщением)
QADD8 знаковое сложение соответствующих байт двух операндов (с насыщением)
QASX знаковое сложение младшего полуслова второго операнда и старшего полуслова первого операнда,  знаковое вычитание старшего полуслова второго операнда из младшего полуслова первого операнда (с насыщением)
QDADD удвоение второго операнда, суммирование результата с первым операндом (знаковое, с насыщением)
QDSUB удвоение второго операнда, вычитание результата из первого операнда (знаковое, с насыщением)
QSAX знаковое вычитание младшего полуслова второго операнда из старшего полуслова первого операнда + знаковое сложение младшего полуслова первого операнда и старшего полуслова второго операнда (с насыщением)
QSUB знаковое вычитание (с насыщением)
QSUB16 знаковое вычитание соответствующих полуслов двух операндов (с насыщением)
QSUB8 знаковое вычитание соответствующих байт двух операндов (с насыщением)
SADD16 знаковое сложение соответствующих полуслов двух операндов
SADD8 знаковое сложение соответствующих байт двух операндов
SASX знаковое сложение старшего полуслова первого пперанда и младшего полуслова второго операнда с записью в старшее полуслово результата,  знаковое вычитание младшего полуслова второго операнда из старшего полуслова первого с записью в младшее полуслово результата
SEL выбор байтов из операндов в соответствии с битами GE[3:0] («флаги», устанавливающиеся при выполнении различных условий типа «больше или равно» при выполнении арифметических операций)
SHADD16 знаковое сложение соответствующих полуслов операндов, сдвиг двух результатов на один бит вправо
SHADD8 знаковое сложение соответствующих байт операндов, сдвиг четырех результатов на один бит вправо
SHASX знаковое сложение старшего полуслова первого операнда и младшего полуслова второго операнда, запись результата в старшее полуслово указанного регистра со сдвигом вправо на один бит, знаковое вычитание старшего полуслова второго операнда из младшего полуслова первого операнда, запись результата в младшее полуслово указанного регистра со сдвигом вправо на один бит
SHSAX знаковое вычитание  младшего полуслова второго операнда из старшего полуслова первого операнда, запись результата в младшее полуслово указанного регистра со сдвигом вправо на один бит, знаковое сложение старшего полуслова второго операнда и младшего полуслова первого операнда, запись результата в старшее полуслово указанного регистра со сдвигом вправо на один бит
SHSUB16 знаковое вычитание старшего и младшего полуслов второго операнда из соответствующих полуслов первого операнда, сдвиг результат на бит вправо
SHSUB8 знаковое вычитание старшего и младшего байт второго операнда из соответствующих байтов первого операнда, сдвиг результат на бит вправо
SMLABB, SMLABT, SMLATB, SMLATT умножение верхних или нижних полуслов двух операндов с 32-разрядным накоплением
SMLAD,  SMLADX попарное умножение полуслов двух операндов, суммирование двух произведение с 32-разрядным накоплением
SMLALBB, SMLALBT, SMLALTB, SMLALTT умножение знаковых полуслов двух операндов (старших или младших) с 64-разрядным накоплением и 64-разрядным результатом
SMLALD,  SMLALDX попарное умножение двух байт, взятых из первого операнда на два байта из второго операнда, суммирование двух полученных произведений с 64-разрядным накоплением и 64-разрядным результатом
SMLAWB, SMLAWT умножение верхнего или нижнего полуслова первого операнда на второй операнд с 32-разрядным накоплением, в результирующий регистр записываюся первые 32 разряда 48-битного результата
SMLSD вычитание произведения старших полуслов двух операндов из младших полуслов двух операндов с 32-разрядным накоплением
SMLSLD вычитание произведения старших полуслов двух операндов из младших полуслов двух операндов с 64-разрядным накоплением
SMMLA умножение двух операндов с 32-разрядным накоплением (берутся только 32 старших разряда произведения)
SMMLS,  SMMLR умножение двух операндов, вычитание результата из указанного регистра (берутся только 32 старших разряда произведения)
SMMUL, SMMULR умножение операндов (результат — старшие 32-разрядна произведения)
SMUAD умножение старших полуслов двух операндов, умножение младших полуслов двух операндов, сложение произведений
SMULBB, SMULBT SMULTB, SMULTT умножение верхних или нижних полуслов двух оперндов
SMULWB,  SMULWT умножение первого операнда на верхнее или нижнее полуслово второго операнда, в результирующий регистр записываюся первые 32 разряда 48-битного результата
SMUSD,  SMUSDX умножение старших полуслов двух операндов, умножение младших полуслов двух операндов, вычитание первого произведения из второго
SSAT16 знаковое насыщение полуслов до указанного значения
SSAX знаковое вычитание младшего полуслова второго операнда из старшего полуслова первого операнда с записью в младшее полуслово результата, сложение старшего полуслова первого операнда и младшего полуслова второго операнда с записью в старшее полуслово результата
SSUB16 знаковое вычитание соответствующих полуслов двух операндов
SSUB8 знаковое вычитание соответствующих байт двух операндов
SXTAB извлечение бит [7:0] из регистра и их преобразование в 32-разрядное слово с учетом знака, сложение результата со словом или полусловом
SXTAB16 извлечение бит [7:0] и [23:16] из регистра, их преобразование в полуслова  с учетом знака, сложение результата со словом или полусловом
SXTAH извлечение бит [15:0] из регистра и их преобразование в 32-разрядное слово  с учетом знака, сложение результата со словом или полусловом
SXTB16 преобразование двух байт в два полуслова с учетом знака, сложение результата со словом или полусловом
UADD16 беззнаковое сложение соответствующих полуслов двух операндов
UADD8 беззнаковое сложение соответствующих байт двух операндов
USAX сложение младшего полуслова первого операнда и старшего полуслова второго операнда с записью результата в младшее полуслово результата, беззнаковое вычитание младшего полуслова второго операнда из старшего полуслова первого операнда с записью в старшее полуслово результата
UHADD16 беззнаковое сложение соответствующих полуслов двух операндов и сдвиг результатов на один бит вправо
UHADD8 беззнаковое сложение соответствующих байт двух операндов и сдвиг результатов на один бит вправо
UHASX беззнаковое сложение старшего полуслова первого операнда и младшего полуслова второго операнда со сдвигом результата сложения на один бит вправо и записью в старшее полуслово результата, беззнаковое вычитание старшего полуслова второго операнда из младшего полуслова первого операнда со сдвигом результата вычитания на один бит вправо и записью в младшее полуслово результата 
UHSAX беззнаковое вычитание млдашего полуслова второго операнда из старшего полуслова первого операнда со сдвигом результата вычитания на один бит вправо и записью в старшее полуслово результата, беззнаковое сложение младшего полуслова первого операнда и старшего полуслова второго операнда со сдвигом результата сложения на один бит вправо и записью в младшее полуслово результата,
UHSUB16 беззнаковое вычитание соответствующих полуслов двух операндов, сдвиг результата на один бит вправо
UHSUB8 беззнаковое вычитание соответствующих байт двух операндов, сдвиг результата на один бит вправо
UMAAL беззнаковое умножение с двойным 32-разрядным накоплением и 64-разряжным результатом
UQADD16 беззнаковое сложение 16-разрядных переменных (с насыщением)
UQADD8 беззнаковое сложение 8-разрядных переменных (с насыщением)
UQASX беззнаковое вычитание младшего полуслова второго операнда из старшего полуслова первого операнда, беззнаковое сложение младшего полуслова первого операнда и старшего полуслова второго операнда (с насыщением)
UQSAX беззнаковое вычитание младшего полуслова второго операнда из старшего полуслова первого операнда, беззнаковое сложение младшего полуслова первого операнда и старшего полуслова второго операнда (с насыщением)
UQSUB16 беззнаковое вычитание соответствующих полуслов двух операндов (с насыщением)
UQSUB8 беззнаковое вычитание соответствующих байт двух операндов (с насыщением)
USAD8 беззнаковое вычитание соответствующих байт двух операндов, сложение абсолютных разностей
USADA8 беззнаковое вычитание соответствующих байт двух операндов, сложение абсолютных разностей, сложение результат операции с содержимым аккумулятора
USAT16 беззнаковое насыщение полуслов до указанного значения
UASX беззнаковое вычитание старшего полуслова второго операнда из младшего полуслова первого операнда с записью в младшее полуслово результата, сложение старшего полуслова первого операнда и младшего полуслова второго операнда с записью результата в старшее полуслова результата
USUB16 беззнаковое вычитание соответствующих полуслов двух операндов
USUB8 беззнаковое вычитание соответствующих байт двух операндов
UXTAB извлечение бит [7:0] из регистра и их преобразование в 32-разрядное слово без учета знака, сложение результата со словом или полусловом
UXTAB16 извлечение бит [7:0] и [23:16] из регистра, их преобразование в полуслова  без учета знака, сложение результата со словом или полусловом
UXTAH извлечение бит [15:0] из регистра и их преобразование в 32-разрядное слово  без учета знака, сложение результата со словом или полусловом
UXTB16 преобразование двух байт в два полуслова без учета знака, сложение результата со словом или полусловом
FPU-инструкции ядра Cortex-M4F
 
 
Команда Операция
VABS.F32 получение абсолютного значения операнда
VADD.F32 сложение операндов
VCMP.F32 сравнение двух операндов или операнда и нуля
VCMPE.F32 сравнение двух операндов или операнда и нуля с проверкой на некорректный операнд (NaN)
VCVT.S32.F32 преобразование между типами данных (с плавающей точкой / целые)
VCVT.S16.F32 преобразование между типами данных  (с плавающей точкой / с фиксированной точкой)
VCVTR.S32.F32 преобразование между типами данных (с плавающей точкой / целые) с округлением
VCVT.F32.F16 преобразование между типами данных (полуслово с плавающей точкой — оно же «число с половинной точностью» / с плавающей точкой)
VCVTT.F32.F16 преобразование между типами данных (с плавающей точкой / полуслово с плавающей точкой)
VDIV.F32 деление операндов
VFMA.F32 перемножение двух переменных, прибавление результата умножения к содержимому указанного регистра  
VFNMA.F32 инвертирование первого опренда, умножение результата на второй операнд, сложение произведения и инвертированного значения из указанного регистра 
VFMS.F32 инвертирование первого опренда, умножение результата на второй операнд, сложение произведения и значения из указанного регистра 
VFNMS.F32 умножение двух операндов, сложение произведения и инвертированного значения из указанного регистра 
VLDM.F<32|64> извлечение содержимого нескольких указанных регистров из памяти программ
VLDR.F<32|64> извлечение содержимого указанного регистра из памяти программ
VLMA.F32 умножение с накоплением
VLMS.F32 вычитание произведения двух операндов из указанного регистра
VMOV пересылки данных между «стандартными» регистрами ARM и регистрами FPSCR (Floating-Point Status and Control Register), пересылки данных между регистрами хранящими формата с плавающей точкой (регистры FPU), запись констант в регистры FPU и т.п.
VMOV, VMRS, VMSR пересылки данных между «стандартными» регистрами ARM и регистрами FPSCR (Floating-Point Status and Control Register)
VMUL.F32 умножение операндов
VNEG.F32 инвертирование
VNMLA.F32 умножение двух операндов, инвентирование результата, сложение инвертированого произведения и инвертированного значения из указанного регистра
VNMLS.F32 умножение двух операндов, произведения и инвертированного значения из указанного регистра
VNMUL умножение двух операндов, инвентирование результата
VPOP таки pop
VPUSH таки push
VSQRT.F32 извлечение квадратного корня
VSTM сохранение содержимого нескольких указанных регистров в память программ
VSTR.F<32|64> сохранение содержимого указанного регистра в память программ
VSUB.F<32|64> вычитание операндов

Впрочем, на практике сами инструкции ядра используются не часто. Обычно при разработке достаточно разобраться с документацией на контроллер и сишными библиотеками от производителей ядра и кристалла. В частности, для ядер Cortex существует ARM-овский набор библиотек CMSIS, который используется для процессоров Cortex-M от разных производителей. В состав CMSIS входит и библиотека CMSIS-DSP, она включает в себя:

  • базовые математические функции, операции над векторами
  • быстрые тригонометрические и трансцендентные функции (sin, cos, sqrt и т.д.)
  • линейную и билинейную интерполяции
  • комплексную арифметику
  • статистические функции
  • алгоритмы фильтрации — БИХ-, КИХ- фильтры, алгоритм минимальной среднеквадратичной ошибки
  • алгоритмы преобразования сигналов (БПФ и др.)
  • матричную арифметику
  • ПИД-регулятор
  • функции для работы с массивами

Часть практическая

Как правило, сравнение ядер Cortex-M3 и Cortex-M4(F) заканчивается красивыми графиками — гистограммами, на которых показано значительное ускорение работы контроллера на базе -M4 при выполнении типовых для ЦОС операций (КИХ-фильтр, БПФ, матричные вычисления, ПИД-регулятор и т.п). Без указаний используемых контроллеров, методики вычислений и измерений.

Но мы не будем сравнивать Тайд и Обычный стиральный порошок, а возьмем реальную аппаратную и программную платформу.

На этом месте есть смысл отвлечься и немного поразмышлять задачах, для которых актуален описанный математический аппарат Cortex-M4F. Понятно, что на работу с потоковыми данными и разным мультимедиа производительности не хватит, речь идет скорее о системах управления и обработки данных.
Например, идет сбор каких-то телеметрических данных. Узел опроса датчиков может либо просто управлять датчиками и передавать массивы данных на некий центральный вычислительный узел, либо самостоятельно обрабатывать и фильтровать результаты измерений, передавая «центру» только полезные данные. Второй подход имеет ряд очевидных преимуществ — уменьшаются затраты на коммуникации, за счет распределенных вычислений повышается надежность системы и упрощается её масштабирование.
Я не имею в виду только большие и сложные распределенные системы. Представьте себе какой-нибудь модный фитнес-браслет. Будет он передавать всё что намерил на вашем пульсе через bluetooth смартфону? Конечно, нет. Браслет сам проанализирует данные и отправит только одну маленькую посылочку с результатом.
И вот мы подошли к главному. Что чаще всего важно контроллера, который осуществляет вычисления «на месте»? Энергопотребление! Чем меньше варемени занимает обработка данных, тем больше времени микроконтроллер проводит в режиме сна и тем дольше устройство работает без подзарядки.

Так мы вполне логично дошли до того чтобы рассмотреть микроконтроллеры серии EFM32 Wonder Gecko от SiLabs. Они классные и вы можете купить их в ЭФО по отличным ценам оптом и в розницу. Кхе-кхе.

EFM32WG — серия микроконтроллеров на базе ядра Cortex-M4F. Как и другие EFM32, они ориентированы на малопотребляющие устройства и предоставляют разные программные и аппаратные средства для контроля над энергопотреблением. Эти средства мы будем использовать для сравнения ядер Cortex-M3 и Cortex-M4F.

Аппаратная часть:

Плата EFM32WG-STK3800 — кит для работы с микроконтроллером на базе ядра Cortex-M4F
Плата EFM32GG-STK3700 — кит для работы с микроконтроллером на базе ядра Cortex-M3
Платы отличатся между собой только целевым микроконтроллером.
71fd92f7578a489c9f1355689e9d846c.png

Программная часть
В описываемом эксперимента использовалась платформа Simplicity Studio. Это SiLabs-овская оболочка, которая объединяет все программы, утилиты, примеры и документы, доступные для микроконтроллеров от Silabs. Сейчас понадобятся несколько её компонентов — IDE, утилита для контроля энергопотребления energy profiler, а также готовый проект из набора примеров и user guide на используемые платы.

Суть эксперимента
Одна из программ из набора готовых примеров использует Быстрое Преобразование Фурье для измерения частоты мерцания внешнего источника света. Если вкратце, то сигнал с датчика освещенности поступает на АЦП, результаты измерений буферизируются, и раз в 0,5 сек производятся вычисления: по выборке из 512 результатов измерений с использованием БПФ выделяется частота основной гармоники. На ЖКИ выводится результат вычислений и количество машинных циклов за которые была исполнена функция ProcessFFT ().
Ресурсоемкой является только часть алгоритма, связанная с анализом измерений. Запустим одну и ту же программу на двух платах и сравним длительность вычислений и уровень энергопотребления.

665769c2004b44299661bb6b9bb578c5.jpg

Открываем Simplicity Studio, включаем Simplicity IDE, компилируем проект.

Листинг программы
Мопед не мой, приведенный файл — один из дефолтных примеров, доступных как в Simplicity IDE, так и в IAR, Keil и некоторых других средах разработки.
/***************************************************************************//**
 * @file lightsensefft.c
 * @brief FFT transform example
 * @details
 *   Use ADC in order to capture and analyse input from the
 *   light sensor on the STK. Runs floating point FFT algorithm from the CMSIS
 *   DSP Library, and estimate the frequency of the most luminous light source
 *   using sinc interpolation. The main point with this example is to show the
 *   use of the CMSIS DSP library and the floating point capability of the CPU.
 *
 * @par Usage
 *   Connect the light sensor output to the ADC input by shorting pins
 *   15 and 14 on the EXP_HEADER of the STK.
 *   Direct various light sources to the light sensor. Expect no specific
 *   frequency from daylight or from a flashlight. Mains powered incandescent
 *   bulbs should give twice the mains frequency. Using another STK running the
 *   "blink" example modified to various blink rates is an excellent signal
 *   source. The frequency bandwidth is approximately 10-500 Hz.
 *   The frequency shows in the 4 digit numerical display upper right on
 *   the LCD. The LCD also displays the number of CPU cycles used to do
 *   the FFT transform.
 *
 * @author Silicon Labs
 * @version 1.04
 *******************************************************************************
 * @section License
 * (C) Copyright 2014 Silicon Labs, http://www.silabs.com
 *******************************************************************************
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 *    claim that you wrote the original software.
 * 2. Altered source versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
 *
 * DISCLAIMER OF WARRANTY/LIMITATION OF REMEDIES: Silicon Labs has no
 * obligation to support this Software. Silicon Labs is providing the
 * Software "AS IS", with no express or implied warranties of any kind,
 * including, but not limited to, any implied warranties of merchantability
 * or fitness for any particular purpose or warranties against infringement
 * of any proprietary rights of a third party.
 *
 * Silicon Labs will not be liable for any consequential, incidental, or
 * special damages, or any other relief, or for any claim by any third party,
 * arising from your use of this Software.
 *
 ******************************************************************************/
#include "em_common.h"
#include "em_emu.h"
#include "em_cmu.h"
#include "em_chip.h"
#include "em_adc.h"
#include "em_gpio.h"
#include "em_rtc.h"
#include "em_acmp.h"
#include "em_lesense.h"
#include "segmentlcd.h"
#include "arm_math.h"
#include "math.h"

/**
 * Number of samples processed at a time. This number has to be equal to one
 * of the accepted input sizes of the rfft transform of the CMSIS DSP library.
 * Increasing it gives better resolution in the frequency, but also a longer
 * sampling time.
 */
#define BUFFER_SAMPLES                  512

/** (Approximate) sample rate used for sampling data. */
#define SAMPLE_RATE                     (1024)

/** The GPIO pin used to power the light sensor. */
#define EXCITE_PIN    gpioPortD,6

/* Default configuration for alternate excitation channel. */
#define LESENSE_LIGHTSENSE_ALTEX_DIS_CH_CONF                                    \
  {                                                                             \
    false,                  /* Alternate excitation enabled.*/                  \
    lesenseAltExPinIdleDis, /* Alternate excitation pin is disabled in idle. */ \
    false                   /* Excite only for corresponding channel. */        \
  }

/* ACMP */
#define ACMP_NEG_REF           acmpChannelVDD
#define ACMP_THRESHOLD         0x38                         /* Reference value for the lightsensor.
                                                             * Value works well in office light 
                                                             * conditions. Might need adjustment 
                                                             * for other conditions. */
/* LESENSE Pin config */
#define LIGHTSENSE_CH             6
#define LIGHTSENSE_EXCITE_PORT    gpioPortD
#define LIGHTSENSE_EXCITE_PIN     6
#define LIGHTSENSE_SENSOR_PORT    gpioPortC
#define LIGHTSENSE_SENSOR_PIN     6
#define LCSENSE_SCAN_FREQ         5
#define LIGHTSENSE_INTERRUPT      LESENSE_IF_CH6

/** Buffer of uint16_t sample values ready to be FFT-ed. */
static uint16_t lightToFFTBuffer[BUFFER_SAMPLES];

/** Buffer of float samples ready for FFT. */
static float32_t floatBuf[BUFFER_SAMPLES];

/** Complex (interleaved) output from FFT. */
static float32_t fftOutputComplex[BUFFER_SAMPLES * 2];

/** Magnitude of complex numbers in FFT output. */
static float32_t fftOutputMag[BUFFER_SAMPLES];

/** Flag used to indicate whether data is ready for processing */
static volatile bool dataReadyForFFT;
/** Indicate whether we are currently processing data through FFT */
static volatile bool processingFFT;

/** Instance structures for float32_t RFFT */
static arm_rfft_instance_f32 rfft_instance;
/** Instance structure for float32_t CFFT used by the RFFT */
static arm_cfft_radix4_instance_f32 cfft_instance;

/**************************************************************************//**
 * Interrupt handlers prototypes
 *****************************************************************************/
void LESENSE_IRQHandler(void);

/**************************************************************************//**
 * Functions prototypes
 *****************************************************************************/
void setupCMU(void);
void setupACMP(void);
void setupLESENSE(void);

/**************************************************************************//**
 * @brief LESENSE_IRQHandler
 * Interrupt Service Routine for LESENSE Interrupt Line
 *****************************************************************************/
void LESENSE_IRQHandler(void)
{
  /* Clear interrupt flag */
  LESENSE_IntClear(LIGHTSENSE_INTERRUPT);

}

/***************************************************************************//**
 * @brief Enables LFACLK and selects osc as clock source for RTC
 ******************************************************************************/
void RTC_Setup(CMU_Select_TypeDef osc)
{
  RTC_Init_TypeDef init;

  /* Ensure LE modules are accessible */
  CMU_ClockEnable(cmuClock_CORELE, true);

  /* Enable osc as LFACLK in CMU (will also enable oscillator if not enabled) */
  CMU_ClockSelectSet(cmuClock_LFA, osc);

  /* Division prescaler to decrease consumption. */
  CMU_ClockDivSet(cmuClock_RTC, cmuClkDiv_32);

  /* Enable clock to RTC module */
  CMU_ClockEnable(cmuClock_RTC, true);

  init.enable   = false;
  init.debugRun = false;
  init.comp0Top = true; /* Count only to top before wrapping */
  RTC_Init(&init);
  
  /* RTC clock divider is 32 which gives 1024 ticks per second.  */
  RTC_CompareSet(0, ((1024 * SAMPLE_RATE) / 1000000)-1);
    
  /* Enable interrupt generation from RTC0, needed for WFE (wait for event). */
  /* Notice that enabling the interrupt in the NVIC is not needed. */
  RTC_IntEnable(RTC_IF_COMP0);
}

/**************************************************************************//**
 * @brief  Enable clocks for all the peripherals to be used
 *****************************************************************************/
void setupCMU(void)
{
  /* Ensure core frequency has been updated */
  SystemCoreClockUpdate();

  /* Set the clock frequency to 11MHz so the ADC can run on the undivided HFCLK */
  CMU_HFRCOBandSet(cmuHFRCOBand_11MHz);  
  
  /* ACMP */
  CMU_ClockEnable(cmuClock_ACMP0, true);

  /* GPIO */
  CMU_ClockEnable(cmuClock_GPIO, true);

  /* ADC */
  CMU_ClockEnable(cmuClock_ADC0, true);
  
/* Low energy peripherals
 *   LESENSE
 *   LFRCO clock must be enables prior to enabling
 *   clock for the low energy peripherals */
  CMU_ClockSelectSet(cmuClock_LFA, cmuSelect_LFRCO);
  CMU_ClockEnable(cmuClock_CORELE, true);
  CMU_ClockEnable(cmuClock_LESENSE, true);

  /* RTC */
  CMU_ClockEnable(cmuClock_RTC, true);

  /* Disable clock source for LFB clock. */
  CMU_ClockSelectSet(cmuClock_LFB, cmuSelect_Disabled);
}

/**************************************************************************//**
 * @brief  Sets up the ACMP
 *****************************************************************************/
void setupACMP(void)
{
  /* Configuration structure for ACMP */
  static const ACMP_Init_TypeDef acmpInit =
  {
    .fullBias                 = false, /* The lightsensor is slow acting, */
    .halfBias                
    
            

© Habrahabr.ru