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

в 14:25, , рубрики: SiLabs, Блог компании ЭФО, отладка, программирование микроконтроллеров, продам микроконтроллер, Разработка для интернета вещей, системное программирование, метки: ,

На что стоит променять Cortex-M3? - 1ARM 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.

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

По сравнению с 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, набор команд каждого ядра включает в себя все команды, поддерживаемые в более младших ядрах. Так обеспечивается возможность разработки программно-совместимых микроконтроллеров на базе разных ядер, этим и занимается большинство производителей микроконтроллеров.

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

Ядра 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

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

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

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

SMLALD      R6, R8, R5, R1

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

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

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

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

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

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

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

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

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

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

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

Для формата чисел с плавающей запятой двойной точности, как в 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<B|H>.F32.F16 преобразование между типами данных (полуслово с плавающей точкой — оно же "число с половинной точностью" / с плавающей точкой)
VCVTT<B|T>.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
Платы отличатся между собой только целевым микроконтроллером.
На что стоит променять Cortex-M3? - 8

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

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

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

Открываем 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
 * <b>(C) Copyright 2014 Silicon Labs, http://www.silabs.com</b>
 *******************************************************************************
 *
 * 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                 = true,  /* comparator bias current can be set to lowest setting.*/
    .biasProg                 = 0x0,   /* Analog comparator will still be fast enough */
    .interruptOnFallingEdge   = false, /* No comparator interrupt, lesense will issue interrupts. */
    .interruptOnRisingEdge    = false, 
    .warmTime                 = acmpWarmTime512, /* Not applicable, lesense controls this. */
    .hysteresisLevel          = acmpHysteresisLevel5, /* Some hysteresis will prevent excessive toggling. */
    .inactiveValue            = false, /* Not applicable, lesense controls this. */
    .lowPowerReferenceEnabled = false, /* Can be enabled for even lower power. */
    .vddLevel                 = 0x00,  /* Not applicable, lesense controls this through .acmpThres value. */
    .enable                   = false  /* Not applicable, lesense controls this. */
  };

  /* Initialize ACMP */
  ACMP_Init(ACMP0, &acmpInit);
  /* Disable ACMP0 out to a pin. */
  ACMP_GPIOSetup(ACMP0, 0, false, false);
  /* Set up ACMP negSel to VDD, posSel is controlled by LESENSE. */
  ACMP_ChannelSet(ACMP0, acmpChannelVDD, acmpChannel0);
  /* LESENSE controls ACMP thus ACMP_Enable(ACMP0) should NOT be called in order
   * to ensure lower current consumption. */
}

/**************************************************************************//**
 * @brief  Sets up the LESENSE
 *****************************************************************************/
void setupLESENSE(void)
{
  /* LESENSE configuration structure */
  static const LESENSE_Init_TypeDef initLesense =
  {
    .coreCtrl         =
    { /* LESENSE configured for periodic scan. */
      .scanStart    = lesenseScanStartPeriodic,
      .prsSel       = lesensePRSCh0,
      .scanConfSel  = lesenseScanConfDirMap,
      .invACMP0     = false,
      .invACMP1     = false,
      .dualSample   = false,
      .storeScanRes = false,
      .bufOverWr    = true,
      .bufTrigLevel = lesenseBufTrigHalf,
      .wakeupOnDMA  = lesenseDMAWakeUpDisable,
      .biasMode     = lesenseBiasModeDutyCycle, /* Lesense should duty cycle comparator and related references etc. */
      .debugRun     = false
    },

    .timeCtrl         =
    {
      .startDelay     = 0 /* No start delay needed for this application. */
    },

    .perCtrl          =
    {  /* DAC is not needed for this application. */
      .dacCh0Data     = lesenseDACIfData,
      .dacCh0ConvMode = lesenseDACConvModeDisable,
      .dacCh0OutMode  = lesenseDACOutModeDisable,
      .dacCh1Data     = lesenseDACIfData,
      .dacCh1ConvMode = lesenseDACConvModeDisable,
      .dacCh1OutMode  = lesenseDACOutModeDisable,
      .dacPresc       = 0,
      .dacRef         = lesenseDACRefBandGap,
      .acmp0Mode      = lesenseACMPModeMuxThres, /* Allow LESENSE to control ACMP mux and reference threshold. */
      .acmp1Mode      = lesenseACMPModeMuxThres,
      .warmupMode     = lesenseWarmupModeNormal  /* Normal mode means LESENSE is allowed to dutycycle comparator and reference. */
    },

    .decCtrl          =
    { /* Decoder or statemachine not used in this code example. */
      .decInput  = lesenseDecInputSensorSt,
      .initState = 0,
      .chkState  = false,
      .intMap    = true,
      .hystPRS0  = false,
      .hystPRS1  = false,
      .hystPRS2  = false,
      .hystIRQ   = false,
      .prsCount  = true,
      .prsChSel0 = lesensePRSCh0,
      .prsChSel1 = lesensePRSCh1,
      .prsChSel2 = lesensePRSCh2,
      .prsChSel3 = lesensePRSCh3
    }
  };

  /* Channel configuration */
  /* Only one channel is configured for the lightsense application. */
  static const LESENSE_ChDesc_TypeDef initLesenseCh =
  {
    .enaScanCh     = true, 
    .enaPin        = false,                 /* Pin is input, no enabling needed. Separate pin is exciting the sensor. */
    .enaInt        = true,                  /* Enable interrupt for this channel. */
    .chPinExMode   = lesenseChPinExHigh,    /* Excite by pullin pin high. */
    .chPinIdleMode = lesenseChPinIdleDis,   /* During Idle, excite pin should be disabled (tri-stated). */
    .useAltEx      = true,                  /* Use alternate excite pin. */
    .shiftRes      = false,                 /* Not applicable, only for decoder operation. */
    .invRes        = false,                 /* No need to invert result. */
    .storeCntRes   = true,                  /* Not applicable, don't care really. */
    .exClk         = lesenseClkLF,          /* Using low frequency clock for timing the excitation. */
    .sampleClk     = lesenseClkLF,          /* Using low frequency clock for timing the sample instant. */
    .exTime        = 0x01,                  /* 1 LFclk cycle is enough excitation time, this depends on response time of light sensor. */
    .sampleDelay   = 0x01,                  /* Sampling should happen when excitation ends, it it happens earlier, excitation time might as well be reduced. */
    .measDelay     = 0x00,                  /* Not used here, basically only used for applications which uses the counting feature. */
    .acmpThres     = ACMP_THRESHOLD,        /* This is the analog comparator threshold setting, determines when the acmp triggers. */
    .sampleMode    = lesenseSampleModeACMP, /* Sampling acmp, not counting. */
    .intMode       = lesenseSetIntLevel,  /* Interrupt when voltage goes above threshold. */
    .cntThres      = 0x0000,                /* Not applicable. */
    .compMode      = lesenseCompModeLess    /* Not applicable. */
  };

  /* Alternate excitation channels configuration. */
  /* The lightsensor is excited by alternate excite channel 0. */
  static const LESENSE_ConfAltEx_TypeDef initAltEx =
  {
    .altExMap = lesenseAltExMapALTEX,
    .AltEx[0] =
    {
      .enablePin = true,
      .idleConf  = lesenseAltExPinIdleDis,
      .alwaysEx  = true
    },
    .AltEx[1] = LESENSE_LIGHTSENSE_ALTEX_DIS_CH_CONF,
    .AltEx[2] = LESENSE_LIGHTSENSE_ALTEX_DIS_CH_CONF,
    .AltEx[3] = LESENSE_LIGHTSENSE_ALTEX_DIS_CH_CONF,
    .AltEx[4] = LESENSE_LIGHTSENSE_ALTEX_DIS_CH_CONF,
    .AltEx[5] = LESENSE_LIGHTSENSE_ALTEX_DIS_CH_CONF,
    .AltEx[6] = LESENSE_LIGHTSENSE_ALTEX_DIS_CH_CONF,
    .AltEx[7] = LESENSE_LIGHTSENSE_ALTEX_DIS_CH_CONF
  };

  /* Initialize LESENSE interface _with_ RESET. */
  LESENSE_Init(&initLesense, true);

  /* Configure LESENSE channel */
  LESENSE_ChannelConfig(&initLesenseCh, LIGHTSENSE_CH);

  /* Configure alternate excitation channels */
  LESENSE_AltExConfig(&initAltEx);

  /* Set scan frequency */
  LESENSE_ScanFreqSet(0, LCSENSE_SCAN_FREQ);

  /* Set clock divisor for LF clock. */
  LESENSE_ClkDivSet(lesenseClkLF, lesenseClkDiv_2);


}

/**************************************************************************//**
 * @brief  Sets up the GPIO
 *****************************************************************************/
void setupGPIO(void)
{
  /* Configure the drive strength of the ports for the light sensor. */
  GPIO_DriveModeSet(LIGHTSENSE_EXCITE_PORT, gpioDriveModeStandard);
  GPIO_DriveModeSet(LIGHTSENSE_SENSOR_PORT, gpioDriveModeStandard);

  /* Initialize the 2 GPIO pins of the light sensor setup. */
  GPIO_PinModeSet(LIGHTSENSE_EXCITE_PORT, LIGHTSENSE_EXCITE_PIN, gpioModePushPull, 0);
  GPIO_PinModeSet(LIGHTSENSE_SENSOR_PORT, LIGHTSENSE_SENSOR_PIN, gpioModeDisabled, 0);
}

/**************************************************************************//**
 * @brief Configure ADC for 12 bit mode, sample channel 0 with Vdd as reference 
 * and use shortest acquisition time.
 *****************************************************************************/
static void ADC_Config(void)
{
  
  CMU_ClockEnable(cmuClock_ADC0, true);
  
  ADC_Init_TypeDef       init       = ADC_INIT_DEFAULT;
  ADC_InitSingle_TypeDef singleInit = ADC_INITSINGLE_DEFAULT;

  /* Init common settings for both single conversion and scan mode- */
  /* Set timebase to 10, this gives 11 cycles which equals 1us at 11 MHz. */
  init.timebase = 10;

  /* Set ADC clock prescaler to 0, we are using 11MHz HFRCO, which results in HFPERCLK < 13MHz- */
  init.prescale = 0;

  ADC_Init(ADC0, &init);

  /* Init for single conversion use, measure channel 0 with Vdd as reference. */
  /* Using Vdd as reference removes the 5us warmup time for the bandgap reference. */
  singleInit.reference  = adcRefVDD;
  singleInit.input      = adcSingleInpCh5;
  
  /* Resolution can be set lower for even more energy efficient operation. */
  singleInit.resolution = adcRes8Bit;

  /* Assuming we are mesuring a low impedance source we can safely use the shortest */
  /* acquisition time. */
  singleInit.acqTime = adcAcqTime1;

  ADC_InitSingle(ADC0, &singleInit);
  
  /* Enable ADC Interrupt when Single Conversion Complete. */
  /* This is necessary for WFE (wait for event) to work. */
  /* Notice that enabling the interrupt in the NVIC is not needed. */  
  ADC0->IEN = ADC_IEN_SINGLE;
}

/**************************************************************************//**
 * @brief A separate function for taking all the samples is preferred since 
 * the whole idea is to stay in EM2 between samples. If other code is added, 
 * it might be more energy efficient to configure the ADC to use DMA while 
 * the cpu can do other work. 
 *****************************************************************************/
void doAdcSampling(uint16_t* buffer)
{  
  uint16_t sample_count = 0;
  
  /* Enable RTC, this can be enabled all the time as well if needed. */
  RTC_Enable(true);
  
  while(sample_count < BUFFER_SAMPLES)
  {    
    /* Enable deep sleep to enter EM2 between samples. */
    SCB->SCR = SCB_SCR_SEVONPEND_Msk | SCB_SCR_SLEEPDEEP_Msk;
    
    /* Go to sleep while waiting for RTC event (set by RTC_IRQ pending bit) */
    /* Since IRQ is not enabled in the NVIC, no ISR will be entered */
    __WFE();

    /* Start ADC conversion as soon as we wake up. */
    ADC_Start(ADC0, adcStartSingle);
    
    /* Clear the interrupt flag */
    RTC_IntClear(RTC_IF_COMP0);
    
    /* Clear pending RTC IRQ */
    NVIC_ClearPendingIRQ(RTC_IRQn);

    /* Wait while conversion is active in EM1, should be almost finished since it */
    /* takes 13 cycles + warmup (1us), and it was started a while ago. */
    /* Disable deep sleep so we wait in EM1 for conversion to finish. */
    SCB->SCR = SCB_SCR_SEVONPEND_Msk; 
    __WFE();
    
    /* Clear the interrupt flag */
    ADC_IntClear(ADC0, ADC_IF_SINGLE);
    
    /* Clear pending IRQ */
    NVIC_ClearPendingIRQ(ADC0_IRQn);
    
    /* Get ADC result */
    buffer[sample_count++] = ADC_DataSingleGet(ADC0);
   
  }
  
  RTC_Enable(false);
}

/***************************************************************************//**
* @brief
*   Process the sampled data through FFT.
*******************************************************************************/
void ProcessFFT(void)
{
  uint16_t        *inBuf;
  int32_t         value;
  int             i;

  inBuf = lightToFFTBuffer;

  /*
   * Convert to float values.
   */
  for (i = 0; i < BUFFER_SAMPLES; ++i)
  {
    value = (int32_t)*inBuf++;
    floatBuf[i] = (float32_t)value;
  }

  /* Process the data through the RFFT module, resulting complex output is
   * stored in fftOutputComplex
   */
  arm_rfft_f32(&rfft_instance, floatBuf, fftOutputComplex);

  /* Compute the magnitude of all the resulting complex numbers */
  arm_cmplx_mag_f32(fftOutputComplex,
                    fftOutputMag,
                    BUFFER_SAMPLES);
}

/***************************************************************************//**
* @brief
*   Find the maximal bin and estimate the frequency using sinc interpolation.
* @return
*   Frequency of maximal peak
*******************************************************************************/
float32_t GetFreq(void)
{
  float32_t maxVal;
  uint32_t maxIndex;

  /* Real and imag components of maximal bin and bins on each side */
  float32_t rz_p, iz_p, rz_n, iz_n, rz_0, iz_0;
  /* Small correction to the "index" of the maximal bin */
  float32_t deltaIndex;
  /* Real and imag components of the intermediate result */
  float32_t a, b, c, d;

  #define START_INDEX 4
  /* Find the biggest bin, disregarding the first bins because of DC offset and
   * low frequency noise.
   */
  arm_max_f32(&fftOutputMag[START_INDEX],
              BUFFER_SAMPLES / 2 - START_INDEX,
              &maxVal,
              &maxIndex);

  maxIndex += START_INDEX;

  /* Perform sinc() interpolation using the two bins on each side of the
   * maximal bin. For more information see page 113 of
   * http://tmo.jpl.nasa.gov/progress_report/42-118/118I.pdf
   */

  /* z_{peak} */
  rz_0 = fftOutputComplex[maxIndex * 2];
  iz_0 = fftOutputComplex[maxIndex * 2 + 1];

  /* z_{peak+1} */
  rz_p = fftOutputComplex[maxIndex * 2 + 2];
  iz_p = fftOutputComplex[maxIndex * 2 + 2 + 1];

  /* z_{peak-1} */
  rz_n = fftOutputComplex[maxIndex * 2 - 2];
  iz_n = fftOutputComplex[maxIndex * 2 - 2 + 1];

  /* z_{peak+1} - z_{peak-1} */
  a = rz_p - rz_n;
  b = iz_p - iz_n;
  /* z_{peak+1} + z_{peak-1} - 2*z_{peak} */
  c = rz_p + rz_n - (float32_t)2.0 * rz_0;
  d = iz_p + iz_n - (float32_t)2.0 * iz_0;

  /* Re (z_{peak+1} - z_{peak-1}) / (z_{peak+1} + z_{peak-1} - 2*z_{peak}) */
  deltaIndex = (a*c + b*d) / (c*c + d*d);

  return ((float32_t)maxIndex + deltaIndex)
          * (float32_t)SAMPLE_RATE
          / (float32_t)BUFFER_SAMPLES;
}

/***************************************************************************//**
* @brief
*   Main function. Setup ADC, FFT, clocks, PRS, DMA, Timer,
*   and process FFT forever.
*******************************************************************************/
int main(void)
{
  uint32_t    time;
  arm_status  status;

  /* Chip errata */
  CHIP_Init();

  /* Enable clocks for used peripherals */
  setupCMU();  
  
  /* Setup the ACMP */
  setupACMP();
  
  /* Setup the GPIO */
  setupGPIO();
  
  /* setup lesense */
  setupLESENSE();
  
  /* Enable LCD without voltage boost */
  SegmentLCD_Init(false);
  SegmentLCD_Symbol(LCD_SYMBOL_GECKO, 1);
  SegmentLCD_Symbol(LCD_SYMBOL_EFM32, 1);

  /* Initialize the CFFT/CIFFT module */
  status = arm_rfft_init_f32(&rfft_instance,
                             &cfft_instance,
                             BUFFER_SAMPLES,
                             0,  /* forward transform */
                             1); /* normal, not bitreversed, order */

  if (status != ARM_MATH_SUCCESS) {
    /* Error initializing RFFT module. */
    SegmentLCD_Write(" Error ");
    while (1) ;
  }
  
  /* Configure RTC to use LFXO as clock source */
  RTC_Setup(cmuSelect_LFXO);
    
  /* Configure ADC */
  ADC_Config();

  /* Enable DWT */
  CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
  /* Make sure CYCCNT is running */
  DWT->CTRL |= 1;
  
  while (1)
  {
      /* Power the light sensor with GPIO. */
      GPIO_PinModeSet( EXCITE_PIN, gpioModePushPull, 1);
      
      /* Do sampling. */
      doAdcSampling(lightToFFTBuffer);
      
      /* Power off the light sensor. */
      GPIO_PinModeSet( EXCITE_PIN, gpioModeDisabled, 0);
      
      /* Do FFT, measure number of cpu cycles used. */
      time = DWT->CYCCNT;
      ProcessFFT();
      time = DWT->CYCCNT - time;

      /* Display dominant frequency. */
      SegmentLCD_Number( (int)GetFreq() );

      /* Display cpu cycle count used to do FFT. */
      SegmentLCD_LowerNumber( (int)time );
      
      /* Check last ADC value to determine if lightlevel is too low. */
      /* Go to sleep with lesense enabled if ADC reading is below 10. */
      if(lightToFFTBuffer[BUFFER_SAMPLES-1] < 10)
      {
        
        /* Write to LCD that lightlevel is too low. */
        SegmentLCD_NumberOff();
        SegmentLCD_Write("DARK");
        
        /* Set gpio in pushpull for lesense operation. */
        GPIO_PinModeSet(LIGHTSENSE_EXCITE_PORT, LIGHTSENSE_EXCITE_PIN, gpioModePushPull, 0);
        
        LESENSE->ROUTE = LESENSE_ROUTE_ALTEX0PEN; 
        /* Start scan. */
        LESENSE_ScanStart();
        
        /* Enable deep sleep to enter EM2. */
        SCB->SCR = SCB_SCR_SEVONPEND_Msk | SCB_SCR_SLEEPDEEP_Msk;
        /* Go to sleep while waiting for LESENSE event */
        /* Since IRQ is not enabled in the NVIC, no ISR will be entered */
        __WFE();

        /* Clear interrupt flag */
        LESENSE_IntClear(LIGHTSENSE_INTERRUPT);
    
        /* Clear pending RTC IRQ */
        NVIC_ClearPendingIRQ(LESENSE_IRQn);    
        
        LESENSE_ScanStop();
        LESENSE->ROUTE &= ~LESENSE_ROUTE_ALTEX0PEN;        
        
      }
      
  }
}

Поехали
Подключаем плату в режиме DBG, программируем микроконтроллер, подключаем вход АЦП к выходу интерфейса датчиков (через него опрашивается light sensor), запускаем.
Вывод первый: программа работает корректно. Когда работает лампа дневного света, результат работы вычислений — 100 Гц (в сети переменного тока частота 50 Гц, а «максимальная интенсивность» света включенной в сеть лампы достигается и на минимуме, и на максимуме синусоиды, т.е. дважды за период). Помещая датчик освещенности в тень получаем результат «DARK», а при естественном освещении — «прыгающие» цифры.

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

Теперь воспользуемся утилитой energy profiler, которая предоставляет график изменения уровня энергопотребления, обновляющийся по ходу исполнения программы.
Запускаем профилирование для платы EFM32WG-STK3800.

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

На вычисления у микроконтроллера EFM32WG990F256 ушло около 49 мс, среднее энергопотребление — 411 мкА. Запомним этот результат и попробуем запустить ту же сишную программу на модуле с микроконтроллером на базе Cortex-M3, то есть без всяких DSP- и FPU-инструкций ядра.

В свойствах проекта для этого необходимо

изменить целевой микроконтроллер с EFM32WG990F256 на EFM32GG990F1024,

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

объяснить компилятору, транслятору и линковщику что программа теперь собирается для кристалла на базе cortex-m3 вместо cortex-m4,

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

отключить опцию использования аппаратного fpu.

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

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

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

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

Результаты можно сравнивать с чистой совестью: оба кристалла работают с тактовой частотой 48 МГц, опрос датчиков и обработка данных идут с одинаковой периодичностью, результаты выводятся в одном и том же формате на одинаковые ЖКИ.

По графикам видно, что энергопотребление кристалла действительно почти полностью определяется уровнем потребления на этапах вычислений и вывода их результатов. Измерения же проводятся в режиме «сна» и практически не влияют на общее энергопотребление. Вычисления на ядре Cortex-M3 проводятся в 2.2 раза медленнее, в той же пропорции изменяется и среднее энергопотребление устройства.

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

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

Автор: ЭФО

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js