Критичні розділи на Cortex-M3


10

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

У моєму випадку я запускаю LPC1758 і у мене є приймач TI CC2500. CC2500 має штифти, які можна використовувати як лінії переривання для даних у буфері RX та вільний простір у буфері TX.

Як приклад, я хочу мати буфер TX в SRAM моєї MCU, і коли в буфері TX приймача є вільний простір, я хочу записати ці дані туди. Але рутина, яка вводить дані в буфер SRAM, очевидно, не може бути перервана перериванням вільного простору в TX. Тому я хочу зробити тимчасове відключення переривань під час виконання цієї процедури заповнення цього буфера, але будь-які переривання, що виникають під час цієї процедури, виконуються після її закінчення.

Як це найкраще робиться на Cortex-M3?

Відповіді:


11

Cortex M3 підтримує корисну пару операцій операцій (поширених також у багатьох інших машинах) під назвою "Ексклюзивний навантаження" (LDREX) та "Ексклюзивний магазин" (STREX). Концептуально операція LDREX виконує навантаження, а також встановлює якесь спеціальне обладнання, щоб спостерігати, чи може завантажене місце може бути записане чимось іншим. Виконання STREX до адреси, використовуваної останньою LDREX, призведе до того, що ця адреса буде записана, лише якщо спочатку нічого іншого не написав . Інструкція STREX завантажить реєстр з 0, якщо магазин відбувся, або 1, якщо він перерваний.

Зауважте, що STREX часто песимістичний. Існує безліч ситуацій, коли це може вирішити не працювати в магазині, навіть якщо б фактично не торкнулося відповідного місця. Наприклад, переривання між LDREX і STREX призведе до того, що STREX припустить, що місце, яке спостерігається, може бути потрапило. З цієї причини зазвичай корисно мінімізувати кількість коду між LDREX та STREX. Наприклад, розгляньте щось подібне:

inline void safe_increment (uint32_t * addr)
{
  uint32_t new_value;
  робити
  {
    new_value = __ldrex (addr) + 1;
  } while (__ strex (new_value, addr));
}

яка компілює щось на зразок:

; Припустимо, що R0 має вказану адресу; r1 пошкоджено
lp:
  ldrex r1, [r0]
  додайте r1, r1, # 1
  strex r1, r1, [r0]
  cmp r1, # 0; Перевірте, чи немає нуля
  bne lp
  .. код продовжується

Переважна більшість часу виконання коду між LDREX і STREX нічого не відбудеться, щоб "потривожити" їх, тому STREX матиме успіх без подальшої помилки. Якщо, однак, після вказівки LDREX або ADD трапиться переривання, STREX не виконає магазин, а натомість код повернеться до зчитування (можливо оновленого) значення [r0] та обчислить нове збільшення значення виходячи з цього.

Використання LDREX / STREX для формування таких операцій, як safe_increment, дає змогу не тільки керувати критичними розділами, але й у багатьох випадках уникати потреби в них.


Тож немає ніякого способу "блокувати" переривання, щоб їх можна було подавати знову, коли вони розблоковані? Я розумію, що це, мабуть, неелегантне рішення, навіть якщо це можливо, але я просто хочу дізнатися більше про обробку переривань ARM.
Еміль Еріксон

3
Можна відключити переривання, і на Cortex-M0 часто немає практичної альтернативи цьому. Я вважаю, що підхід LDREX / STREX є більш чистим, ніж відключення переривань, хоча, правда, у багатьох випадках це насправді не має значення (я думаю, що включити і відключити в кінцевому підсумку один цикл, а відключення переривань протягом п'яти циклів, мабуть, не велика справа) . Зауважте, що підхід ldrex / strex буде працювати, якщо код переміщений до багатоядерного процесора, тоді як підхід, який відключає переривання, не буде. Також деякі запущені коди RTOS із зменшеними дозволами, які не дозволяють вимикати переривання.
supercat

Я, мабуть, все-таки буду з FreeRTOS так чи інакше, тому не буду займатися цим самим, але все одно хотів би навчитися. Який спосіб відключення перерв я повинен використовувати для блокування перерв, як описано, на відміну від того, щоб відкинути будь-які перерви, що виникають під час процедури? Як би я це зробив, якщо хочу їх відкинути?
Еміль Ерікссон

Одній з наведених відповідей не можна довіряти, оскільки в асоційованому коді відсутня дужка: while(STREXW(new_value, addr); Як ми можемо вважати, що те, що ви говорите, є правильним, якщо ваш код навіть не складеться?

@Tim: Вибачте, моє введення тексту не ідеальне; У мене немає фактичного коду, який я написав як корисний для порівняння, тому я не пам'ятаю, чи використовувала система, яку я використовував, STREXW або __STREXW, але посилання компілятора перераховує __strex як внутрішню (на відміну від STREXW, яка обмежена 32-бітний STREX, внутрішнє використання __strex генерує STREXB, STREXH або STREX залежно від розміру вказівника)
supercat

4

Здається, що вам потрібні кілька кругових буферів або FIFO у вашому програмному забезпеченні MCU. Відстежуючи два індекси або покажчики в масиві для читання і запису, ви можете мати передній план і фон, що мають доступ до одного буфера без втручання.

Код переднього плану може в будь-який час записуватися до кругового буфера. Він вставляє дані вказівник запису, потім збільшує покажчик запису.

Фон (код переривання) використовує дані з вказівника читання і збільшує вказівник читання.

Коли вказівники читання і запису рівні, буфер порожній, а фоновий процес не надсилає даних. Коли буфер заповнений, процес переднього плану відмовляється писати більше (або може замінити старі дані, залежно від ваших потреб).

Використання круглих буферів для декупажу читачів і авторів повинно усунути необхідність відключення переривань.


Так, я, очевидно, буду використовувати кругові буфери, але збільшення та зменшення не є атомними операціями.
Еміль Ерікссон

3
@Emil: Вони не повинні бути. Для класичного кругового буфера з двома вказівниками та одним "непридатним" слотом все, що потрібно, - це те, що записи пам'яті є атомними та виконуються в порядку. Читач володіє одним вказівником, письменник - іншим, і, хоча вони обидва можуть читати будь-який вказівник, його вказівник пише лише власник вказівника. У цей момент все, що вам потрібно, - це атомний порядок запису.
Джон Р. Стром

2

Я не можу згадати точне місце розташування, але в бібліотеках, що надходять від ARM (не TI, ARM, він повинен бути під CMSIS або щось подібне, я використовую ST, але я пам’ятаю, де читав, що цей файл прийшов з ARM, так що ви також повинні мати його ) є глобальна опція відключення переривання. Це виклик функції. (Я не на роботі, але завтра підшу точну функцію). Я б завершив це приємним іменем у вашій системі і відключив переривання, зробіть свою справу і ввімкніть знову. Сказавши це, кращим варіантом буде реалізація семафору або структури черги замість глобального відключення переривання.


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.