Подтвердить что ты не робот

Как держать прерывания короткими?

Самый известный совет во встроенном программировании - "держать прерывания короткими".

Теперь моя ситуация в том, что у меня очень длинная работа в моем цикле main() (запись больших блоков данных на SDcard), которые иногда могут принимать 100 мс. Поэтому, чтобы поддерживать мою систему в ответе, я переместил все остальные вещи в обработчики прерываний.

Например, как правило, обрабатывать входящие данные UART в прерывании, а затем обрабатывать входящую команду в цикле main(), а затем отправлять обратно ответ. Но в моем случае вся обработка/обработка команд также занимает места в прерываниях, потому что мой цикл main() может быть заблокирован для (относительно) длительных периодов.

Оптимальным решением было бы перейти на RTOS, но у меня нет ОЗУ для него. Существуют ли альтернативы для моего дизайна, где прерывания могут быть короткими?

4b9b3361

Ответ 1

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

После завершения прерывания список отложенных процедур проходит от наиболее важных до наименее важных.

Рассмотрим случай, когда у вас есть основное (нижнее значение) и два прерывания I1 и I2, где I2 является более важным, чем основной, но менее важным, чем I1.

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

Затем планировщик возвращается к I1 DPC (потому что это более важно), и когда I1 DPC завершается, начинается I2 DPC (потому что он более важен, чем основной), а затем в конечном итоге возвращает выполнение в main.

Эта конструкция позволяет вам запланировать важность различных прерываний, поощрять вас к тому, чтобы ваши прерывания были небольшими, и позволяет вам завершить DPC упорядоченным и упорядоченным по порядку способом.

Ответ 2

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

(Отказ от ответственности: мой первый выбор, как правило, является преимущественным ядром реального времени, многие из них могут работать в системах с ограниченными ресурсами). Предложение SecurityMatt - это хорошо, но если вам неудобно реализовать свой собственный превентивный переключатель ядра/задачи, особенно тот, который обрабатывает асинхронное (прерывание) срабатывание, вы можете быстро обернуть вокруг оси. Поэтому я предлагаю ниже не так отзывчиво, как ядро, основанное на preemption, но оно намного проще и часто адекватно).

Создайте 3 очереди событий/работ:

  • Q1 является самым низким приоритетом и обрабатывает вашу медленную фоновый SD-карту.
  • Q2 содержит запросы на обработку входящих пакетов UART
  • Q3 (самый высокий приоритет) содержит запросы чтения UIF RX FIFO.

Я разделил чтение UIF RX FIFO и обработку прочитанного пакета так, чтобы чтение FIFO всегда обслуживалось перед обработкой пакетов; возможно, вы хотите сохранить их вместе, ваш выбор.

Чтобы это работало, вы разбиваете свой большой (~ 100 мс) процесс записи SD-карты на кучу меньших, дискретных, выполняемых шагов завершения.

Так, например, чтобы написать 5 блоков по 20 мс каждый, вы пишете первый блок, а затем запишите "записать следующий блок" в Q1. Вы возвращаетесь к своему планировщику в конце каждого шага и просматриваете очереди в порядке приоритета, начиная с Q3. Если Q2 и Q3 пусты, вы вытащите следующее событие из Q1 ( "напишите следующий блок" ) и запустите эту команду еще на 20 мс, прежде чем возвращать и снова проверять очереди. Если 20 мс недостаточно чувствительны, вы разбиваете каждую запись в 20 мс на более мелкий набор шагов, постоянно отправляя на Q1 следующий рабочий шаг.

Теперь для входящего материала UART; в UART RX ISR вы просто запустите команду "прочитать команду UART FIFO" в Q3 и вернитесь из прерывания обратно в 20 мс "блок записи", который был прерван. Как только процессор завершит запись, он вернется и сканирует очереди в порядке приоритета (наихудший ответ будет 20 мс, если запись блока только началась во время прерывания). Сканер очередей (планировщик) увидит, что теперь у Q3 есть работа, и он будет запускать эту команду перед возвратом и повторным сканированием.

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

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

Этот управляемый событиями подход с приоритетными очередями - это точно подход, используемый платформой Quantum Platform (QP), управляемой событиями. QP фактически поддерживает базовый неперехватный (кооперативный) планировщик, такой как описанный здесь, или упреждающий планировщик, который запускает планировщик, каждый из которых помещен в очередь (аналогично подходу, предложенному SecurityMatt). Вы можете увидеть код/​​реализацию планировщика сотрудничества QP на веб-сайте QP.

Ответ 3

Альтернативное решение будет следующим:

В любом случае библиотека FAT может захватывать процессор в течение длительного времени, вы вставляете вызов новой функции, которая обычно очень быстрая и возвращается к вызывающей стороне после нескольких машинных циклов. Такая быстрая функция не повлияет на производительность вашего времени в режиме реального времени, например, чтение/запись на SD Flash. Вы должны вставить такой вызов в любой цикл, который ожидает, что сектор флэш-памяти будет удален. Вы также вставляете вызов этой функции между каждыми 512 байтами или 512 байтами.

Цель этой функции - выполнить большую часть задачи, которую вы обычно должны иметь внутри цикла while (1) в типичном "main()" для встроенного устройства. Сначала он увеличил бы целое число и выполнил бы быстрый модул по новому значению, а затем вернул бы, если модуль не равен произвольной константе. Код выглядит следующим образом:

void premption_check(void)
{
    static int fast_modulo = 0;
    //divide the number of call
    fast_modulo++;
    if( (fast_modulo & 0x003F) != 3)
    {
        return;
    }
    //the processor would continue here only once every 64 calls to "premption_check"

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

Используемая двоичная маска 0x3F означает, что мы смотрим только на 6 младших значащих бит счетчика. Когда эти 6 бит оказываются равными произвольному значению 5, когда идут вызовы функций, которые могут занять несколько микросекунд или даже миллисекунды. Возможно, вы захотите попробовать меньшую или большую двоичную маску в зависимости от скорости, с которой вы хотите обслуживать последовательный порт и другие операции. Вы можете использовать одновременно несколько масок для обслуживания некоторой операции быстрее других.

Библиотека FAT и SD-карта не должны испытывать никаких проблем, например, когда между двумя операциями стирания Flash происходит некоторая спорадическая задержка.

Решение, приведенное здесь, работает даже с микроконтроллером с размером всего 2 Кбайта, как и у многих вариантов 8051. Как ни невероятно, у пинбольной машины с 1980 по 1990 год было несколько К оперативной памяти, медленные процессоры (например, 10 МГц), и они могут тестировать сто коммутаторов... полностью отлаживаются, обновляют матричный дисплей X/Y, производят звуковые эффекты и т.д. Решения, разработанные этим инженером, все еще могут использоваться для повышения производительности большой системы. Даже с лучшими серверами с 64-гигабайтной оперативной памятью и многими терабайтами жесткого диска я полагаю, что любые байты подсчитываются, когда какая-то компания хочет индексировать миллиарды WEB-страниц.

Ответ 4

Поскольку никто не предложил прийти к нему с этой целью, я брошу его в шляпе:

Возможно, что при использовании низкоприоритетного прерывания поддержка SD-карты, возможно, в некоторых DMA, возможно, вы освободите свой основной цикл и другие прерывания, чтобы быть более отзывчивыми, а скорее чем застревание в цикле main(), ожидающем долгого времени для завершения чего-либо.

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

Прибегая к RTOS для чего-то вроде этого, казалось бы излишним и признанием неудачи для меня...;)