Прерывание Arduino (при изменении штифта)

Я использую функцию прерывания, чтобы заполнить массив значениями, полученными из digitalRead().

 void setup() {
      Serial.begin(115200);
       attachInterrupt(0, test_func, CHANGE);
    }

    void test_func(){
      if(digitalRead(pin)==HIGH){
          test_array[x]=1;  
        } else if(digitalRead(pin)==LOW){
          test_array[x]=0;  
        }
         x=x+1;
    }

Эта проблема заключается в том, что при печати test_array существуют такие значения, как: 111 или 000.

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

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

8 голосов | спросил user277820 20 FebruaryEurope/MoscowbFri, 20 Feb 2015 02:33:25 +0300000000amFri, 20 Feb 2015 02:33:25 +030015 2015, 02:33:25

2 ответа


16

Как своего рода пролог для этого слишком длинного ответа ...

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

Клейтон Миллс уже объяснил в своем ответе, что есть некоторое время ожидания в ответ на прерывания. Здесь я остановлюсь на количественном определении латентность (которая огромная при использовании библиотек Arduino), а также означает минимизировать его. В большинстве случаев это относится к аппаратным средствам Arduino Uno и аналогичных плат.

Сведение к минимуму задержки прерывания на Arduino

(или как получить от 99 до 5 циклов)

Я буду использовать исходный вопрос в качестве рабочего примера и повторю проблема с точки зрения задержки прерывания. У нас есть какое-то внешнее событие, которое вызывает прерывание (здесь: INT0 при изменении смены). Нам нужно взять некоторые действие при срабатывании прерывания (здесь: чтение цифрового входа). проблема заключается в том, что между срабатыванием прерывания происходит некоторая задержка и мы принимаем соответствующие меры. Мы называем эту задержку «прерыванием латентность ". Длительная латентность вредна во многих ситуациях. конкретный пример, входной сигнал может меняться во время задержки, в в этом случае мы получаем ошибочное чтение. Мы ничего не можем сделать, чтобы избежать задержка: она является неотъемлемой частью работы прерываний. Мы можем, однако, постарайтесь сделать это как можно короче, что, надо надеяться, плохие последствия.

Первое очевидное, что мы можем сделать, это принять критичное по времени действие, внутри обработчика прерываний, как можно скорее. Это означает вызов digitalRead() один раз (и только один раз) в самом начале обработчик. Вот нулевая версия программы, на которой мы будем построить:

 #define INT_NUMBER 0
#define PIN_NUMBER 2    // interrupt 0 is on pin 2
#define MAX_COUNT  200

volatile uint8_t count_edges;  // count of signal edges
volatile uint8_t count_high;   // count of high levels

/* Interrupt handler. */
void read_pin()
{
    int pin_state = digitalRead(PIN_NUMBER);  // do this first!
    if (count_edges >= MAX_COUNT) return;     // we are done
    count_edges++;
    if (pin_state == HIGH) count_high++;
}

void setup()
{
    Serial.begin(9600);
    attachInterrupt(INT_NUMBER, read_pin, CHANGE);
}

void loop()
{
    /* Wait for the interrupt handler to count MAX_COUNT edges. */
    while (count_edges < MAX_COUNT) { /* wait */ }

    /* Report result. */
    Serial.print("Counted ");
    Serial.print(count_high);
    Serial.print(" HIGH levels for ");
    Serial.print(count_edges);
    Serial.println(" edges");

    /* Count again. */
    count_high = 0;
    count_edges = 0;  // do this last to avoid race condition
}

Я протестировал эту программу и последующие версии, отправив ее поездам импульсов различной ширины. Между импульсами достаточно промежутка чтобы не было пропущено ни одного края: даже если принимается задний фронт перед выполнением предыдущего прерывания второй запрос прерывания будет быть отложенным и в конечном итоге обслуживаться. Если импульс короче задержка прерывания, программа считывает 0 на обоих краях. Сообщенные количество уровней HIGH - это процент правильных импульсов считывания.

Что происходит при срабатывании прерывания?

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

В большинстве случаев входящее прерывание обслуживается сразу. Это может однако, что MCU (что означает «микроконтроллер») находится в середине некоторой критически важной задачи, когда обслуживание прерываний отключен. Обычно это происходит, когда он уже обслуживает другое прерывание. Когда это происходит, запрос входящего прерывания переносятся на удержание и обслуживаются только тогда, когда это критическое время выполняется. Эта ситуация трудно полностью избежать, поскольку существует довольно немногие из этих критических разделов в основной библиотеке Arduino (которые я буду вызов « libcore » в следующем). К счастью, эти разделы короткие и работают только так часто. Таким образом, большую часть времени наше прерывание запрос будет обслуживаться сразу. В дальнейшем я буду считать что мы не заботимся о тех немногих случаях, когда это не так.

Затем наш запрос выполняется немедленно. Это все еще связано с большим количеством вещи, которые могут занять некоторое время. Во-первых, есть жесткая последовательность. MCU завершит выполнение текущей инструкции. К счастью, большинство инструкции однократные, но некоторые могут занимать до четырех циклов. Затем MCU очищает внутренний флаг, который отключает дальнейшее обслуживание перебивает. Это предназначено для предотвращения вложенных прерываний. Затем ПК сохраняется в стеке. Стек представляет собой область ОЗУ, зарезервированную для этого вид временного хранения. ПК (что означает « Program Counter ») - это внутренний регистр, содержащий адрес следующей команды, MCU собирается выполнить. Это то, что позволяет MCU знать, что делать дальше, и сохранение это важно, потому что его нужно будет восстановить в порядке для возобновления основной программы, с которой она была прервана. ПК затем загружается жестким адресом, специфичным для полученного запроса, и это конец жесткой последовательности, а остальные - программное управление.

Теперь MCU выполняет инструкцию с этого жесткого адреса. Эта инструкция называется « вектором прерывания » и обычно является «прыжком», инструкция, которая приведет нас к специальной подпрограмме, называемой ISR (" Процедура обслуживания прерываний "). В этом случае ISR называется «__vector_1», a.k.a. «INT0_vect», что является неправильным, поскольку оно ISR, а не вектор. Этот ISR исходит от libcore. Как и любой ISR, он начинается с пролога , который экономит кучу внутреннего процессора регистры в стеке. Это позволит использовать эти регистры и, когда это будет сделано, восстановите их до прежних значений, чтобы не нарушить основную программу. Затем он будет искать обработчик прерываний который был зарегистрирован с помощью attachInterrupt(), и он будет называть это обработчик, который является нашей функцией read_pin() выше. Наша функция будет затем вызовите digitalRead() из libcore. digitalRead() будет смотреть в некоторые таблицы, чтобы сопоставить номер порта Arduino с аппаратным вводом /выводом порт, который он должен прочитать, и связанный номер бит для тестирования. Это также проверьте, есть ли канал ШИМ на этом выводе, который должен быть отключен. Затем он прочитает порт ввода-вывода ... и мы закончили. Ну, мы на самом деле не выполняется обслуживание прерывания, но критически важная задача (чтение порта ввода-вывода), и все это имеет значение, когда мы глядя на задержку.

Вот краткий обзор всего вышеперечисленного, а также связанный с ним задержки в цикле процессора:

  1. hardwired sequence: завершить текущую инструкцию, предотвратить вложенные прерывания, сохранение ПК, адрес загрузки вектора (â ‰ 4 цикла)
  2. выполнить вектор прерывания: перейти к ISR (3 цикла)
  3. Пролог ISR: сохранить регистры (32 цикла)
  4. Основной корпус ISR: найдите и вызовите зарегистрированную пользователем функцию (13 циклов)
  5. read_pin: вызов digitalRead (5 циклов)
  6. digitalRead: найдите соответствующий порт и бит для тестирования (41 цикл)
  7. digitalRead: прочитайте порт ввода /вывода (1 цикл)

Мы будем предполагать наилучший сценарий, с4 цикла для проводная последовательность. Это дает нам полную задержку в 99 секунд, или около 6,2 В с тактовой частотой 16 МГц. В дальнейшем я буду изучите некоторые трюки, которые можно использовать для снижения этой задержки. Они приходят примерно в возрастающем порядке сложности, но все они нуждаются в нас каким-то образом выкопайте внутренности MCU.

Использовать прямой доступ к портам

Очевидной первой целью сокращения латентности является digitalRead(). Эта функция обеспечивает хорошую абстракцию аппаратного обеспечения MCU, но это слишком неэффективны для критически важной работы. Избавиться от этого фактически тривиальным: нам просто нужно заменить его на digitalReadFast(), из digitalwritefast библиотека. Это сокращает задержку почти наполовину за счет небольшой скачать!

Ну, это было слишком легко, чтобы быть забавным, я скорее покажу вам, как это сделать это трудный путь. Цель состоит в том, чтобы заставить нас начать работать на низкоуровневом уровне. Этот метод называется « прямым доступом к порту » и хорошо документирован ссылку Arduino на странице Порт Регистры . В этот точка, это хорошая идея, чтобы скачать и посмотреть на ATmega328P техническое описание . Этот 650-страничный документ может показаться несколько сложным при первом взгляде. Это, однако, хорошо организованы в секции, специфичные для каждого из MCU периферийных устройств и функций. И нам нужно только проверить разделы что касается того, что мы делаем. В этом случае это раздел с именем порты ввода /вывода . Вот краткое изложение того, что мы узнаем из этих чтений:

  • Вывод 2 Arduino на самом деле называется PD2 (то есть порт D, бит 2) на AVR.
  • Мы получаем весь порт D сразу, читая специальный регистр MCU, называемый "PIND".
  • Затем мы проверяем бит номер 2, выполняя побитовое логическое и (C â € ~ & оператор) с помощью 1 << 2.

Итак, вот наш модифицированный обработчик прерываний:

 #define PIN_REG    PIND  // interrupt 0 is on AVR pin PD2
#define PIN_BIT    2

/* Interrupt handler. */
void read_pin()
{
    uint8_t sampled_pin = PIN_REG;            // do this first!
    if (count_edges >= MAX_COUNT) return;     // we are done
    count_edges++;
    if (sampled_pin & (1 << PIN_BIT)) count_high++;
}

Теперь наш обработчик будет читать регистр ввода-вывода, как только он будет вызван. Задержка составляет 53 цикла процессора. Этот простой трюк спас нам 46 циклов!

Напишите свой собственный ISR

Следующей целью обрезки цикла является INTO_vect ISR. Этот ISR необходимо для обеспечения функциональности attachInterrupt(): мы можем изменять обработчики прерываний в любое время во время выполнения программы. Однако, хотя приятно иметь, это не очень полезно для нашей цели. Таким образом, вместо того, чтобы найти ISR в libcore и вызвать наше прерывание обработчика, мы сэкономим несколько циклов на , заменив на ISR нашим обработчик.

Это не так сложно, как кажется. ISR могут быть написаны как обычные функций, мы просто должны знать их конкретные имена и определять они используют специальный макрос ISR() из avr-libc. На этом этапе хорошо, чтобы взглянуть на документацию Avr-Libc на перебивает , и в разделе Datasheet с именем Внешние прерывания . Здесь краткое резюме:

  • Нам нужно записать немного в специальном аппаратном регистре EICRA ( Регистр управления внешним прерыванием A ), чтобы настроить прерывание, которое должно срабатывать при любом изменении значения штыря. Это будет сделано в setup().
  • Нам нужно записать бит в другом аппаратном регистре EIMSK ( ВнешнийInterrupt MaSK register ), чтобы включить INT0 прерывание. Это также будет сделано в setup().
  • Мы должны определить ISR с синтаксисом ISR(INT0_vect) { ... }.

Вот код для ISR и setup(), все остальное без изменений:

 /* Interrupt service routine for INT0. */
ISR(INT0_vect)
{
    uint8_t sampled_pin = PIN_REG;            // do this first!
    if (count_edges >= MAX_COUNT) return;     // we are done
    count_edges++;
    if (sampled_pin & (1 << PIN_BIT)) count_high++;
}

void setup()
{
    Serial.begin(9600);
    EICRA = 1 << ISC00;  // sense any change on the INT0 pin
    EIMSK = 1 << INT0;   // enable INT0 interrupt
}

Это со свободным бонусом: поскольку этот ISR проще, чем тот, который он заменяет, ему требуется меньше регистров для выполнения своей работы, затем пролог для сохранения реестра короче. Теперь мы отстаем от 20 циклы. Неплохо, учитывая, что мы начали около 100!

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

Напишите голый ISR

Еще здесь? Хорошо! Для дальнейшего, было бы полезно иметь по крайней мере, некоторые основные идеи о том, как работает сборка, и взглянуть на встроенный ассемблер Поваренная из документации avr-libc. На этом этапе наша запись прерывания последовательность выглядит следующим образом:

  1. жесткая последовательность (4 цикла)
  2. вектор прерывания: переход к ISR (3 цикла)
  3. Пролог ISR: save regs (12 циклов)
  4. первая вещь в корпусе ISR: прочитайте порт IO (1 цикл)

Если мы хотим сделать лучше, нам нужно переместить чтение порта в пролог. Идея такова: чтение регистра PIND будет clobber один регистр CPU, поэтому нам нужно сохранить хотя бы один регистр перед этим, но другие регистры могут подождать. Тогда нам нужно напишите собственный пролог, который считывает порт ввода-вывода сразу после сохранения первый регистр. Вы уже видели в прерывании avr-libc документация (вы ее прочитали, правда?), что ISR можно сделать голый , и в этом случае компилятор не выпустит пролог или эпилог, позволяя нам написать нашу собственную версию.

Проблема с этим подходом заключается в том, что мы, вероятно, весь ISR в сборке. Неважно, но я бы предпочел, чтобы компилятор напишет эти скучные прологи и эпилоги для меня. Итак, вот грязный трюк: мы разделим ISR на две части:

  • первая часть будет коротким фрагментом сборки, который будет
    • сохранить один регистр в стек
    • прочитайте PIND в этом регистре
    • сохранить это значение в глобальной переменной
    • восстановить регистр из стека
    • перейти ко второй части
  • вторая часть будет регулярным кодом C с генерируемым компилятором пролог и эпилог

Наш предыдущий INTO ISR затем заменяется следующим:

 volatile uint8_t sampled_pin;    // this is now a global variable

/* Interrupt service routine for INT0. */
ISR(INT0_vect, ISR_NAKED)
{
    asm volatile(
    "    push r0                \n"  // save register r0
    "    in r0, %[pin]          \n"  // read PIND into r0
    "    sts sampled_pin, r0    \n"  // store r0 in a global
    "    pop r0                 \n"  // restore previous r0
    "    rjmp INT0_vect_part_2  \n"  // go to part 2
    :: [pin] "I" (_SFR_IO_ADDR(PIND)));
}

ISR(INT0_vect_part_2)
{
    if (count_edges >= MAX_COUNT) return;     // we are done
    count_edges++;
    if (sampled_pin & (1 << PIN_BIT)) count_high++;
}

Здесь мы используем ISR ()макрос, чтобы иметь инструмент компилятора INT0_vect_part_2 с требуемым прологом и эпилогом. Компилятор будет жаловаться на то, что «â € ~INT0_vect_part_2», похоже, ошибочно обработчик сигнала ", но предупреждение можно безопасно игнорировать. Теперь ISR имеет одна инструкция с двумя циклами перед фактическим чтением порта и общая сумма задержка составляет всего 10 циклов.

Используйте регистр GPIOR0

Что делать, если бы мы могли иметь регистр, зарезервированный для этой конкретной работы? Затем, нам не нужно ничего сохранять, прежде чем читать порт. Мы можем на самом деле попросите компилятор привязать глобальную переменную к Регистр . Это, однако, потребует от нас перекомпиляции всего ядра Arduino и libc, чтобы убедиться, что регистр всегда зарезервирован. На самом деле, нет удобный. С другой стороны, ATmega328P имеет три регистры, которые не используются компилятором или какой-либо библиотекой, и доступный для хранения того, что мы хотим. Они называются GPIOR0, GPIOR1 и GPIOR2 ( регистры ввода /вывода общего назначения ). Хотя они сопоставлены в адресном пространстве ввода-вывода MCU, это фактически not I /O регистры: они представляют собой просто память, например три байта ОЗУ, которые как-то заблудился в автобусе и оказался в неправильном адресном пространстве. Эти не так эффективны, как внутренние регистры процессора, и мы не можем копировать PIND в один из них с помощью команды in. GPIOR0 интересен, хотя в нем бит-адресуемый , как и PIND. Это позволит нам передавать информацию без сглаживания любого внутреннего процессора регистре.

Вот трюк: мы убедимся, что GPIOR0 изначально нулевой (он фактически очищается аппаратными средствами во время загрузки), то мы будем использовать sbic (Пропустить следующую команду, если бит в каком-либо регистре ввода-вывода Clear) и sbi (установить в 1 бит в некоторых регистрах ввода /вывода) инструкции как следующим образом:

 sbic PIND, 2   ; skip the following if bit 2 of PIND is clear
sbi GPIOR0, 0  ; set to 1 bit 0 of GPIOR0

Таким образом, GPIOR0 окажется 0 или 1 в зависимости от бит, который мы хотели для чтения из PIND. Команда sbic принимает 1 или 2 цикла для выполнения в зависимости от того, является ли условие ложным или истинным. Очевидно, что PIND бит осуществляется в первом цикле. В этой новой версии кода глобальная переменная sampled_pin больше не полезна, так как она в основном заменены на GPIOR0:

 /* Interrupt service routine for INT0. */
ISR(INT0_vect, ISR_NAKED)
{
    asm volatile(
    "    sbic %[pin], %[bit]    \n"
    "    sbi %[gpio], 0         \n"
    "    rjmp INT0_vect_part_2  \n"
    :: [pin]  "I" (_SFR_IO_ADDR(PIND)),
       [bit]  "I" (PIN_BIT),
       [gpio] "I" (_SFR_IO_ADDR(GPIOR0)));
}

ISR(INT0_vect_part_2)
{
    if (count_edges < MAX_COUNT) {
        count_edges++;
        if (GPIOR0) count_high++;
    }
    GPIOR0 = 0;
}

Следует отметить, что GPIOR0 всегда должен быть сброшен в ISR.

Теперь выборка регистра ввода-вывода PIND - это первое, что сделано внутри ISR. Общее время ожидания - 8 циклов. Это самое лучшее, что мы можем до того, как они окрашиваются ужасно греховными клопами. Это снова хорошая возможность прекратить читать ...

Поместите критический по времени код в векторную таблицу

Для тех, кто еще здесь, вот наша текущая ситуация:

  1. жесткая последовательность (4 цикла)
  2. вектор прерывания: переход к ISR (3 цикла)
  3. Тело ISR: прочитайте порт IO (в 1-м цикле)

Очевидно, мало возможностей для улучшения. Единственный способ, которым мы могли бы сократить задержку в этой точке - на , заменив вектор прерывания сам по нашему коду. Будьте предупреждены, что это должно быть очень неприятно любому, кто ценит чистое проектирование программного обеспечения. Но это возможно, и я покажет вам, как.

МакетТаблицу векторов ATmega328P можно найти в техническом описании, раздел Прерывания , подраздел Прерывание векторов в ATmega328 и ATmega328P . Или разобрав любую программу для этого чипа. Вот как это выглядит как. Я использую соглашения avr-gcc и avr-libc (__init является вектором 0, адреса находятся в байтах), которые отличаются от Atmel's.

 address │ instruction     │ comment
────────┼─────────────────┼──────────────────────
 0x0000 │ jmp __init      │ reset vector 
 0x0004 │ jmp __vector_1  │ a.k.a. INT0_vect
 0x0008 │ jmp __vector_2  │ a.k.a. INT1_vect
 0x000c │ jmp __vector_3  │ a.k.a. PCINT0_vect
  ...
 0x0064 │ jmp __vector_25 │ a.k.a. SPM_READY_vect

Каждый вектор имеет 4-байтовый слот, заполненный одной командой jmp. Это 32-разрядная команда, в отличие от большинства инструкций AVR, которые 16-битный. Но 32-разрядный слот слишком мал, чтобы удерживать первую часть нашего ISR: мы можем соответствовать инструкциям sbic и sbi, но не rjmp. Если мы это сделаем, таблица векторов закончится следующим образом:

 address │ instruction     │ comment
────────┼─────────────────┼──────────────────────
 0x0000 │ jmp __init      │ reset vector 
 0x0004 │ sbic PIND, 2    │ the first part...
 0x0006 │ sbi GPIOR0, 0   │ ...of our ISR
 0x0008 │ jmp __vector_2  │ a.k.a. INT1_vect
 0x000c │ jmp __vector_3  │ a.k.a. PCINT0_vect
  ...
 0x0064 │ jmp __vector_25 │ a.k.a. SPM_READY_vect

Когда INT0 срабатывает, PIND будет считан, соответствующий бит будет скопирован в GPIOR0, а затем выполнение перейдет к следующему вектору. Затем ISR для INT1 будет вызываться вместо ISR для INT0. Эта жутко, но поскольку мы все равно не используем INT1, мы просто «захватим», его вектор для обслуживания INT0.

Теперь нам просто нужно написать собственную таблицу пользовательских векторов, чтобы переопределить по умолчанию один. Оказывается, это не так просто. Стандартная векторная таблица предоставленный дистрибутивом avr-libc, в объектном файле, называемом crtm328p.o, который автоматически связан с любой программой, которую мы создаем. В отличие от кода библиотеки, код объектного файла не должен быть переопределен: попытка сделать это даст ошибку компоновщика в определении таблицы дважды. Это означает, что мы должны заменить весь crtm328p.o нашим обычная версия. Один из вариантов - скачать полный источник avr-libc кода , сделайте пользовательские изменения в gcrt1.S , затем создайте это как пользовательский libc.

Здесь я пошел на более легкий, альтернативный подход. Я написал обычай crt.S, который является упрощенной версией оригинала от avr-libc. Это не хватает нескольких редко используемых функций, таких как способность определять «уловку» все "ISR, или чтобы иметь возможность завершить программу (то есть заморозить Arduino), вызывая exit(). Вот код. Я обрезал повторяющиеся часть таблицы векторов, чтобы свести к минимуму прокрутку:

 #include <avr/io.h>

.weak __heap_end
.set  __heap_end, 0

.macro vector name
    .weak \name
    .set \name, __vectors
    jmp \name
.endm

.section .vectors
__vectors:
    jmp __init
    sbic _SFR_IO_ADDR(PIND), 2   ; these 2 lines...
    sbi _SFR_IO_ADDR(GPIOR0), 0  ; ...replace vector_1
    vector __vector_2
    vector __vector_3
    [...and so forth until...]
    vector __vector_25

.section .init2
__init:
    clr r1
    out _SFR_IO_ADDR(SREG), r1
    ldi r28, lo8(RAMEND)
    ldi r29, hi8(RAMEND)
    out _SFR_IO_ADDR(SPL), r28
    out _SFR_IO_ADDR(SPH), r29

.section .init9
    jmp main

Его можно скомпилировать с помощью следующей командной строки:

 avr-gcc -c -mmcu=atmega328p silly-crt.S

Эскиз идентичен предыдущему, за исключением того, что нет INT0_vect и INT0_vect_part_2 заменяется на INT1_vect:

 /* Interrupt service routine for INT1 hijacked to service INT0. */
ISR(INT1_vect)
{
    if (count_edges < MAX_COUNT) {
        count_edges++;
        if (GPIOR0) count_high++;
    }
    GPIOR0 = 0;
}

Чтобы скомпилировать эскиз, нам нужна специальная команда компиляции. Если у вас есть после этого вы, вероятно, знаете, как скомпилировать из командной строки. Вы должны явно запросить, чтобы silly-crt.o был связан с вашей программой, и добавьте параметр -nostartfiles, чтобы избежать ссылки в оригинале crtm328p.o.

Теперь чтение порта ввода-вывода является самой первой выполненной инструкцией после триггеров прерывания. Я протестировал эту версию, посылая ее короткой импульсы от другого Arduino, и он может ловить (хотя и не надежно) высокий уровень импульсов составляет 5 циклов. Больше ничего нет. может сделать, чтобы сократить задержку прерывания на этом оборудовании.

ответил Edgar Bonet 27 FebruaryEurope/MoscowbFri, 27 Feb 2015 23:59:05 +0300000000pmFri, 27 Feb 2015 23:59:05 +030015 2015, 23:59:05
6

Прерывание запускается при изменении, а ваш test_func устанавливается как процедура обслуживания прерываний (ISR), вызываемая для обслуживания этого прерывания. Затем ISR печатает значение ввода.

На первый взгляд вы ожидаете, что результат будет таким, как вы сказали, и чередующийся набор высоких минимумов, поскольку он только попадает в ISR при изменении.

Но то, что нам не хватает, - это то, что процессор должен обслуживать прерывание и ветвь в ISR. За это время напряжение на штыре может снова измениться. В частности, если штифт не стабилизируется аппаратным отключением или подобным. Поскольку прерывание уже отмечено и пока не обслуживается, это дополнительное изменение (или многие из них, поскольку уровень штырей может очень быстро измениться относительно тактовой частоты, если он имеет низкую паразитную емкость) будет пропущен.

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

В качестве типичного примера ATmega328 Datasheet , используемого на Arduino Uno детализирует время прерывания в разделе 6.7.1 - «Время отклика прерывания». Для этого микроконтроллера указано минимальное время перехода на ISR для обслуживания - это 4 тактовых цикла, но может быть больше (дополнительно, если выполнение команды с несколькими циклами при прерывании или 8 + время ожидания сна, если MCU находится во сне).

Как упоминалось в комментариях @EdgarBonet, этот вывод также может измениться во время выполнения ISR. Поскольку ISR считывает с булавки дважды, он ничего не добавит к test_array, если он столкнулся с LOW при первом чтении и HIGH на втором. Но x все равно будет увеличиваться, оставив этот слот в массиве неизменным (возможно, как неинициализированные данные в зависимости от того, что было сделано ранее в массиве).

Его однострочный ISR test_array[x++] = digitalRead(pin); - идеальное решение для этого.

ответил Clayton Mills 20 FebruaryEurope/MoscowbFri, 20 Feb 2015 17:41:28 +0300000000pmFri, 20 Feb 2015 17:41:28 +030015 2015, 17:41:28

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132