Критические разделы на 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; Проверьте, не ненулевой
  бн лп
  .. код продолжается

Подавляющее большинство времени выполнения кода, между LDREX и STREX ничего не случится, чтобы «потревожить» их, поэтому STREX преуспеет без дальнейших церемоний. Однако, если прерывание происходит сразу же после инструкции LDREX или ADD, STREX не будет выполнять сохранение, а вместо этого код вернется обратно, чтобы прочитать (возможно, обновленное) значение [r0] и вычислить новое увеличенное значение основываясь на этом.

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


Значит, нет способа «блокировать» прерывания, чтобы их можно было снова обслуживать после разблокировки? Я понимаю, что это, вероятно, не элегантное решение, даже если это возможно, но я просто хочу узнать больше об обработке прерываний ARM.
Эмиль Эрикссон

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

Я, вероятно, в конечном итоге пойду с FreeRTOS, так что сам не буду этим заниматься, но все равно хотел бы научиться. Какой метод отключения прерываний я должен использовать, чтобы заблокировать прерывания, как описано, а не отбрасывать прерывания, возникающие во время процедуры? Как бы мне это сделать, если я хочу отказаться от них?
Эмиль Эрикссон

Нельзя доверять одному приведенному выше ответу, поскольку в связанном коде отсутствует скобка: while(STREXW(new_value, addr); как мы можем поверить в то, что вы говорите правильно, если ваш код даже не скомпилируется?

@Tim: Извините, мой набор текста не идеален; У меня нет фактического кода, который я написал, пригодного для сравнения, поэтому я не помню, использовала ли система, которую я использовал, STREXW или __STREXW, но список ссылок на компилятор перечисляет __strex как встроенный (в отличие от STREXW, который ограничен 32-битный STREX, встроенный в __strex генерирует STREXB, STREXH или STREX в зависимости от предоставленного размера указателя)
суперкат 26.12.14

4

Похоже, вам нужны циклические буферы или FIFO в программном обеспечении MCU. Отслеживая два индекса или указателя в массиве для чтения и записи, вы можете получить доступ к одному и тому же буферу как переднего, так и заднего плана без помех.

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

Фоновый код (обработка прерываний) потребляет данные из указателя чтения и увеличивает указатель чтения.

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

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


Да, я, очевидно, собираюсь использовать циклические буферы, но увеличение и уменьшение не являются атомарными операциями.
Эмиль Эрикссон

3
@ Эмиль: они не должны быть. Для классического кольцевого буфера с двумя указателями и одним «неиспользуемым» слотом все, что необходимо, - это то, что записи в память являются атомарными и применяются по порядку. Читатель владеет одним указателем, писатель владеет другим, и, хотя они оба могут читать любой указатель, только владелец указателя пишет его указатель. На этом этапе все, что вам нужно, это атомарные записи в порядке.
Джон Р. Штром

2

Я не могу вспомнить точное местоположение, но в библиотеках, которые приходят из ARM (не TI, ARM, он должен быть под CMSIS или что-то в этом роде, я использую ST, но я помню, как где-то читал, что этот файл пришел из ARM, поэтому вы должны иметь его также ) есть опция отключения глобального прерывания. Это вызов функции. (Я не на работе, но завтра посмотрю точную функцию). Я хотел бы завершить это с хорошим именем в вашей системе и отключить прерывания, сделать свое дело и включить снова. Сказав это, лучшим вариантом будет реализация семафора или структуры очереди вместо глобального отключения прерывания.


Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.