целостность данных при слишком частых прерываниях

У меня есть кодер, который генерирует ~54000 прерываний в секунду. Это слишком быстро для моего Arduino Uno. Но я все равно пытаюсь понять, что я могу из этого получить. Для науки.

unsigned long tickACount = 0;
unsigned long tickBCount = 0;

void myInterrupt(){
  tickACount++;
  tickBCount++;
}

Затем в моем основном цикле каждую секунду я печатаю tickACount и tickBCount. Я вижу, что мой arduino способен обрабатывать только около 2050 прерываний в секунду. Ничего удивительного.

Что меня удивляет, так это то, что tickACount и tickBCount имеют разные значения! tickBCount постоянно выше, чем tickACount.

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

volatile uint8 tickACount = 0;
volatile uint8 tickBCount = 0;

void myInterrupt(){
  tickACount++;
  tickBCount++;
}

В результате эти два значения все равно различаются. Кроме того, их обёртывание в какой-нибудь макрос ATOMIC не помогает.

Из того, что я прочитал, прерывания прерывать нельзя. И, кроме того, если бы мое прерывание было прервано на полпути, то tickACount был бы больше, чем tickBCount, а не наоборот.

Таким образом, как можно объяснить разницу между этими двумя переменными?

Спасибо за любую идею!

, 👍1

Обсуждение

Они одинаковы. Но когда вы печатаете один, а затем печатаете другой, между ними есть прерывания. Сделайте полный набросок, который показывает проблему. С большим количеством оптимизаций плата uno может делать больше прерываний, чем 54 кГц, возможно, 100 кГц. Учебник по прерываниям: https://gammon.com.au/interrupts, @Jot

Вы можете сэкономить довольно много циклов ЦП, создав свой собственный ISR вместо того, чтобы полагаться на тот, что есть в ядре Arduino, для вызова вашего обработчика прерываний. См. этот ответ, раздел _Напишите свой собственный ISR_., @Edgar Bonet

Если вам нужен простой счетчик, почему бы вам не использовать Timer1 или Timer2 с внешним источником синхронизации?, @KIIV

Рассматривали бы вы использование дополнительного оборудования для снижения нагрузки на микроконтроллер? Как насчет использования регистров сдвига для подсчета импульсов., @MichaelT

Я был убежден, что 54 кГц слишком быстро для Arduino, которая работает на частоте 16 МГц. Это всего ~300 циклов на прерывание + ему нужно обработать обычный цикл. Я заменил прерывания библиотекой Encoder https://github.com/PaulStoffregen/Encoder, и теперь она, похоже, работает нормально. Я немного покопался в этой библиотеке, похоже, она делает примерно то же самое, что и я, но на ассемблере, возможно, с какой-то большой оптимизацией. Молодец, насчет tickCountB > tickCountA. Возможность увеличения tickCountB во время двух отпечатков мне в голову не приходила. Думаю, это опыт!, @OoDeLally


1 ответ


3

54000 прерываний в секунду [...] слишком быстро для моего Arduino уно

Возможно, ваш тест некорректен. Мой Uno справляется с такой частотой прерываний без проблем. Смотрите мой тестовый код ниже.

мой arduino способен обрабатывать только около 2050 прерываний в секунду

Вероятно, вы делаете что-то неправильно, например, блокируете прерывания слишком долго. long или с помощью библиотеки, которая это делает.

Что меня удивляет, так это то, что tickACount и tickBCount имеют разные ценности!

Как уже было сказано в комментариях, это не так. Вместо этого Счетчики были увеличены во время их печати.

И согласно документации, volatile имеет смысл только для 8-битных ценности.

Или документация совершенно неверна, или (что наиболее вероятно) у вас есть неправильно понял.

Из того, что я прочитал, прерывания прерывать нельзя.

Это действительно поведение по умолчанию. Вы можете изменить его, выполнив interrupts() внутри обработчика прерываний или путем объявления ISR как

ISR(XXX_vect, ISR_NOBLOCK)
{
    ...
}

но это делается редко.

Ниже мой тестовый код. Я использовал Timer 2 для генерации прямоугольной волны на контакт 3, который одновременно является контактом ШИМ (OC2B) и контактом прерывания (INT1). Частота ШИМ составляет около 54,054 кГц.

const uint32_t PRINT_PERIOD = 1e6;  // выводить количество раз в секунду

struct TickCounts {
    uint32_t tickA;
    uint32_t tickB;
};

volatile TickCounts rawCounts;

void myInterrupt() {
    rawCounts.tickA++;
    rawCounts.tickB++;
}

// Атомарно считываем оба счетчика.
static TickCounts getCounts() {
    TickCounts counts;
    noInterrupts();
    counts.tickA = rawCounts.tickA;
    counts.tickB = rawCounts.tickB;
    interrupts();
    return counts;
}

void setup() {
    // ШИМ на частоте 54 кГц на OC2B = PD3 = INT1
    DDRD  |= _BV(PD3);    // вывод PD3 как выход
    OCR2A  = 37 - 1;      // период = 37 * 0,5 мкс = 18,5 мкс
    OCR2B  = 37/2 - 1;    // рабочий цикл ~ 50%
    TCCR2A = _BV(COM2B1)  // неинвертирующий ШИМ на OC2B
           | _BV(WGM20)   // режим 7: быстрый ШИМ, TOP = OCR2A
           | _BV(WGM21);  // то же самое
    TCCR2B = _BV(WGM22)   // то же самое
           | _BV(CS21);   // тактовая частота F_CPU/8 = 2 МГц

    Serial.begin(9600);
    attachInterrupt(1, myInterrupt, RISING);
}

void loop() {
    static uint32_t last_time_printed;
    if (micros() - last_time_printed >= PRINT_PERIOD) {
        last_time_printed += PRINT_PERIOD;
        TickCounts counts = getCounts();
        Serial.print(counts.tickA);
        Serial.print(" ");
        Serial.println(counts.tickB);
    }
}

И вот что получилось:

54053 54053
108107 108107
162161 162161
216215 216215
270269 270269
324324 324324
378377 378377
432431 432431
486485 486485
540539 540539
...
,