Точно время вызова функции - PullRequest
2 голосов
/ 17 октября 2008

Я использую микроконтроллер с ядром C51. У меня довольно трудоемкая и большая подпрограмма, которую нужно вызывать каждые 500 мс. ОСРВ не используется.

То, как я сейчас это делаю, заключается в том, что у меня есть существующее прерывание по таймеру 10 мс. Я устанавливаю флаг после каждых 50 прерываний, который проверяется на истинность в главном цикле программы. Если флаг имеет значение true, вызывается подпрограмма. Проблема заключается в том, что к тому времени, когда цикл программы возвращается к обслуживанию флага, он уже превышает 500 мс, иногда даже> 515 мс в случае определенных путей кода. Требуемое время не является точно предсказуемым.

Очевидно, что подпрограмма не может быть вызвана изнутри прерывания таймера из-за того, что для выполнения требуется большое время. Подпрограмма занимает от 50 до 89 мс в зависимости от различных условий.

Есть ли способ гарантировать, что подпрограмма вызывается ровно через 500 мс каждый раз?

Ответы [ 9 ]

1 голос
/ 18 октября 2008

Хороший вариант - использовать RTOS или написать свою собственную простую RTOS.

В ОСРВ для удовлетворения ваших потребностей потребуется только следующее:

  • расписание периодических заданий
  • расписание циклических заданий
  • переключение контекста преформ

Ваши требования следующие:

  • выполнять периодическое задание каждые 500 мс
  • в дополнительное время между выполнением циклических задач (выполнение некритических операций)

Такая ОСРВ гарантирует 99,9% вероятности того, что ваш код будет выполнен вовремя. Я не могу сказать 100%, потому что любые операции, выполняемые вами в ISR, могут мешать работе ОСРВ. Это проблема с 8-битными микроконтроллерами, которые могут одновременно выполнять только одну инструкцию.

Написание RTOS сложно, но выполнимо. Вот пример небольшой (900 строк) ОСРВ, предназначенной для 8-битной платформы AVR ATMEL.

Ниже приводится Отчет и код , созданный для класса CSC 460: Операционные системы реального времени (в Университете Виктории).

1 голос
/ 24 октября 2008

Вы также можете использовать два флага - флаг «перед действием» и флаг «триггер» (используя Майк F в качестве отправной точки):

#define PREACTION_HOLD_TICKS (2)
#define TOTAL_WAIT_TICKS (10)

volatile unsigned char pre_action_flag;
volatile unsigned char trigger_flag;

static isr_ticks;
interrupt void timer0_isr (void) {
   isr_ticks--;
   if (!isr_ticks) {
      isr_ticks=TOTAL_WAIT_TICKS;
      trigger_flag=1;
   } else {
      if (isr_ticks==PREACTION_HOLD_TICKS)
          preaction_flag=1;
   }
}

// ...

int main(...) {


isr_ticks = TOTAL_WAIT_TICKS;
preaction_flag = 0;
tigger_flag = 0;
// ...

   while (1) {
      if (preaction_flag) {
          preaction_flag=0;
          while(!trigger_flag)
             ;
          trigger_flag=0;
          service_routine();
      } else {
          main_processing_routines();
      }
   }
 }
1 голос
/ 17 октября 2008

Я не думаю, что есть способ гарантировать это, но это решение может обеспечить приемлемую альтернативу.

Могу ли я предложить не устанавливать флаг, а вместо этого изменять значение?

Вот как это может работать.

1 / Начать значение с нуля.

2 / Каждое 10 мс прерывание, увеличьте это значение на 10 в ISR (процедура обработки прерывания).

3 / Если в главном цикле значение> = 500, вычтите 500 из значения и выполните действия в течение 500 мс.

При изменении значения вам нужно будет внимательно следить за условиями гонки между таймером и основной программой.

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

Если по какой-то причине ваша функция запускается на 20 мс позже в одной итерации, значение будет уже 520, поэтому ваша функция установит его на 20, то есть будет ждать 480 мс до следующей итерации.

Мне кажется, это лучший способ достичь того, чего вы хотите.

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

volatile int xtime = 0;
void isr_10ms(void)  {
    xtime += 10;
}
void loop(void) {
    while (1) {
        /* Do all your regular main stuff here. */
        if (xtime >= 500) {
            xtime -= 500;
            /* Do your 500ms activity here */
        }
    }
}
1 голос
/ 17 октября 2008

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

Если эта работа должна произойти ровно через 500 мс, запустите ее из процедуры таймера и устраните последствия. Это то, что в любом случае будет делать упреждающая ОСРВ.

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

0 голосов
/ 24 октября 2008

Если я правильно интерпретирую ваш вопрос, у вас есть:

  • основной цикл
  • некоторая операция с высоким приоритетом, которую необходимо запускать каждые 500 мсек, на время до 89 мсек
  • таймер 10 мс, который также выполняет небольшое количество операций.

На мой взгляд, есть три варианта. Первый заключается в использовании второго таймера с более низким приоритетом для ваших операций 500 мс. Вы по-прежнему можете обрабатывать прерывание 10 мс, а после его завершения продолжить обслуживание прерывания по таймеру 500 мс.

Второй вариант - вам действительно нужно обслуживать прерывание 10 мс каждые 10 мс? Делает ли он что-то кроме времени? Если нет, и если ваше оборудование позволит вам определить количество тактов в 10 мс, прошедших при обработке ваших опций 500 мс (т. Е. Не используя сами прерывания), то вы можете запустить свои оперы 500 мс в пределах прерывания 10 мс и обработать 10 мсек, которые вы пропустили, когда закончили.

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

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

(я полагаю, что строго это гибрид вытесняющей и кооперативной многозадачности, но сейчас это не важно)


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

0 голосов
/ 22 октября 2008

Ах, еще одна альтернатива для рассмотрения - архитектура x51 допускает два уровня приоритетов прерываний. Если у вас есть некоторая аппаратная гибкость, вы можете заставить один из внешних выводов прерывания быть поднятым ISR таймера с интервалами 500 мс, а затем позволить обработке прерывания более низкого уровня вашего кода каждые 500 мсек.

В зависимости от вашего конкретного x51, вы также можете генерировать прерывания с более низким приоритетом, полностью встроенные в ваше устройство.

См. Часть 11.2 в этом документе, который я нашел в Интернете: http://www.esacademy.com/automation/docs/c51primer/c11.htm

0 голосов
/ 22 октября 2008

Почему у вас есть срочный режим, который занимает так много времени?

Я согласен с некоторыми другими, что здесь может быть архитектурная проблема.

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

Можете ли вы лучше описать, что делает эта длительная процедура и для чего нужен определенный интервал?


Дополнение на основе комментариев:

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

Например, если ваше прерывание по таймеру установлено на периоды 10 мс, и вы знаете, что процедура обслуживания займет 89 мс, просто продолжайте и подсчитайте 41 прерывание по таймеру, затем выполните свою работу в 89 мс и пропустите восемь прерываний по таймеру. (С 42 по 49).

Затем, когда ваш ISR выходит (и очищает ожидающее прерывание), «первое» прерывание следующего раунда в 500 мс произойдет примерно через мс.

Учитывая, что вы "исчерпали ресурсы", вы предполагаете, что у вас есть другие источники таймера и прерывания, которые также используются - это означает, что надежный таймер основного цикла не сработает, потому что эти другие прерывания Источники могут стрелять не в тот момент.

0 голосов
/ 22 октября 2008

Одним простым решением является прерывание по таймеру, которое срабатывает при 500 мс ...
Если у вас есть некоторая гибкость в дизайне вашего оборудования, вы можете каскадировать вывод одного таймера на счетчик второго каскада, чтобы получить длительную базу. Я забыл, но я смутно припоминаю возможность каскадного таймера на x51.

0 голосов
/ 17 октября 2008

Будет ли это делать то, что вам нужно?

#define FUDGE_MARGIN 2    //In 10ms increments

volatile unsigned int ticks = 0;

void timer_10ms_interrupt( void )  { ticks++; }

void mainloop( void )
{
    unsigned int next_time = ticks+50;

    while( 1 )
    {
        do_mainloopy_stuff();

        if( ticks >= next_time-FUDGE_MARGIN )
        {
            while( ticks < next_time );
            do_500ms_thingy();
            next_time += 50;
        }
    }
}

Примечание: если вы отстали в обслуживании задачи каждые 500 мс, это поставило бы их в очередь, что может быть не тем, что вы хотите.

...