На що варто проміняти Cortex-M3?

ARM 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-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-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



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

Ось більш складна операція: множення старших півслів, множення молодших півслів і підсумовування творів між собою і з 64-розрядним накопиченням. Команда SMLALD описує всі ці дії і виконується Cortex-M4 за один машинний цикл. SMLALD, як і багато інші команди, поєднує множення з накопиченням і обробку даних за принципом SIMD.

SMLALD R6, R8, R5, R1



І прості SIMD-команди (знакові і беззнакові 8 — і 16-розрядні додавання і віднімання тощо), і складні команди, подібні SMLALD, виконуються за один машинний цикл.

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



Серед команд процесорного ядра 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-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
Плати відрізнятись між собою тільки цільовим мікроконтролером.


Програмна частина
В описуваному експерименту використовувалася платформа Simplicity Studio. Це SiLabs-івська оболонка, яка об'єднує всі програми, утиліти, приклади і документи, доступні для мікроконтролерів від Silabs. Зараз знадобляться кілька її компонентів — IDE, утиліта для контролю енергоспоживання energy profiler, а також готовий проект з набору прикладів і user guide на використовувані плати.

Суть експерименту
Одна з програм з набору готових прикладів використовує Швидке Перетворення Фур'є для вимірювання частоти мерехтіння зовнішнього джерела світла. Якщо коротко, то сигнал з датчика освітленості надходить на АЦП, результати вимірювань буферизує, і раз у 0,5 сек виробляються обчислення: по вибірці з 512 результатів вимірювань з використанням ШПФ виділяється частота основної гармоніки. На РКІ виводиться результат обчислень і кількість машинних циклів за які була виконана функція ProcessFFT().
Ресурсномісткою є лише частина алгоритму, пов'язана з аналізом вимірювань. Запустимо одну і ту ж програму на двох платах і порівняємо тривалість обчислень і рівень енергоспоживання.



Відкриваємо 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 чисельного 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>© 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 redistribute it and 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 the power 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 data processing 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 source for clock 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);

/* Clock Enable 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);

/* Interrupt Enable 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, /* interrupt Enable for this channel. */
.chPinExMode = lesenseChPinExHigh, /* by Excite 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 really care. */
.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 while DMA 
* 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 + прогріву (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 used for 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 if enabled 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», а при природному освітленні — «стрибаючі» цифри.



Тепер скористаємося утилітою energy profiler, яка надає графік зміни рівня енергоспоживання, оновлюється по ходу виконання програми.
Запускаємо профілювання для плати EFM32WG-STK3800.



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

У властивостях проекту для цього необхідно
змінити цільової мікроконтролер з EFM32WG990F256 на EFM32GG990F1024,

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


вимкнути опцію використання апаратного fpu.


Природно, в інших IDE процес може проходити декілька інакше, для різних серій мікроконтролерів також можливі різні нюанси, однак принцип переходу на інше ядро буде той же.

Отже, після збереження налаштувань і підключення інший налагоджувальної плати повторимо експеримент.



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

З графіків видно, що енергоспоживання кристала дійсно майже повністю визначається рівнем споживання на етапах обчислень і виведення їх результатів. Вимірювання проводяться в режимі «сну» і практично не впливають на загальне енергоспоживання. Обчислення на ядрі Cortex-M3 проводяться в 2.2 рази повільніше, в тій же пропорції змінюється і середнє енергоспоживання пристрою.

З одного боку, вся математика необхідна для вирішення завдання може виконуватися і на контролері з ядром Cortex-M3, однак різниця у швидкості обчислень може бути істотною для багатьох пристроїв, критичних до енергоспоживання або швидкості роботи.

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

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.