Механизм синхронизации без отключения прерываний на Cortex M0 - PullRequest
2 голосов
/ 05 марта 2019

Для уточнения вопроса предположим, что имеем:

  1. Объект Static Button: static Button_T sButton = {0};
  2. Функция для получения кнопки: void GetButton(Button_T * p_button);, которая вызывается из контекста основного цикла
  3. Обработчик ISR: void ButtonISRHandler(void);

Предположения:

  1. GetButton выполнение может быть прервано любым прерыванием, которое не выполняется ButtonISRHandler
  2. ButtonISRHandler выполнение может быть прервано другими прерываниями
  3. GetButton выполнение занимает меньше времени, чем минимальное время между двумя ButtonISRHandler прерываниями вызова.
  4. Прерывание кнопки - это циклическое прерывание , запускаемое, например, каждые 10 мс.
  5. В ButtonISRHandler у нас есть процедуры, такие как проверка состояния PIN-кода кнопки или обнаружение касания кнопки (в случае сенсорной кнопки). Если данное состояние PIN-кода является стабильным, например, для 5 последовательных вызовов sButton состояние объекта обновляется.
  6. Button_T - общий объект - это может быть классический тактовый переключатель или сенсорная кнопка и т. Д.
  7. ScanButtonAndUpdate может обрабатывать список объектов Button_T, но функция GetButton работает только для объекта с одной кнопкой.

Проблема: классический случай, когда прерывание может произойти, когда счетчик программы находится внутри GetButton

Вопрос: как синхронизировать GetButton с ButtonISRHandler без отключения прерываний ?

Моим целевым процессором является Cortex M0 без операции LDREX / STREX, поэтому я не могу использовать атомы из C11, которые были бы отличным решением в этом случае.

Мое предлагаемое решение

Использовать критический раздел в GetButton.

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

Нет возможности вызвать функцию ScanButtonAndUpdate из прерывания и основного контекста одновременно - это поведение защищено семафором

Осуществление

#define SEMAPHORE_GIVEN                             0
#define SEMAPHORE_TAKEN                             1

typedef uint32_t BaseType_T;
typedef struct Button_T;

static volatile BaseType_T sSemaphore = SEMAPHORE_GIVEN;
static volatile bool sIsPendingISR = false;
static volatile Button_T sButton = {0};

void GetButton(Button_T * p_button)
{
    EnterCriticalSection();

    memcpy(p_button, &sButton, sizeof(Button_T))
    /* Other procedures on sButton... */

    ExitCriticalSection();
}

/* Cyclic executed handler */
void ButtonISRHandler(void)
{
    if (!BinarySemaphoreTake()) {
        SetISRPending();
    }
    else {
        ScanButtonAndUpdate();

        BinarySemaphoreGive();
    }
}

void ScanButtonAndUpdate(void)
{
    /* Scan for instance a current PIN state and update sButton object
       if state is stable in next calls */
}

static void EnterCriticalSection(void)
{
    while(false == BinarySemaphoreTake()) continue;
}

static void ExitCriticalSection(void)
{
    BinarySemaphoreGive();

    if (IsPendingISR()){
        ScanButtonAndUpdate();
        ResetISRPending();
    }
}

static bool BinarySemaphoreTake(void)
{
    if (SEMAPHORE_GIVEN == sSemaphore) {
        /* Value Store operation is atomic on the architecture native type */
        sSemaphore = SEMAPHORE_TAKEN;
        return true;
    }
    else {
        return false;
    }
}

static void BinarySemaphoreGive(void)
{
    sSemaphore = SEMAPHORE_GIVEN;
}

static void SetISRPending(void)
{
    sIsPendingISR = true;
}

static void ResetISRPending(void)
{
    sIsPendingISR = false;
}

static bool IsPendingISR(void)
{
    return sIsPendingISR;
}

Это решение было протестировано и прекрасно работает без проблем, но я не уверен, что это лучшее решение без скрытых ошибок.

РЕДАКТИРОВАТЬ 1: Обновлены предположения и добавлено пропущенное ScanButtonAndUpdate функция

1 Ответ

2 голосов
/ 05 марта 2019

Существует скрытая синхронизация, которая влияет на то, есть ли у вас состояние гонки или нет: что скрывает прерывание? Два наиболее распространенных сценария - запуск по краю и по уровню; триггерный край означает, что прерывание будет заблокировано до тех пор, пока устройство не будет очищено, а триггер уровня означает, что прерывание будет повторно подтверждаться до тех пор, пока устройство не будет очищено.

Если в вашем коде используются прерывания, инициируемые уровнем, то вы полностью пропустили эту синхронизацию или притворяетесь, что sIsPendingISR является маской и флагом состояния. В этом случае ты выглядишь хорошо

Если он инициирован на уровне, он может быть повторно подтвержден во время / * Обновления объекта sButton * / , в результате чего код обработки устройства будет выполняться в двух контекстах (прерывание + нормальное состояние). Большая часть кода устройства не предназначена для этого.

Между прочим, существует программный протокол под названием «Алгоритм Деккерса», который обеспечивает общее решение для взаимного исключения без аппаратной поддержки. Вы как бы интегрировали его версию здесь.

...