есть ли способ дождаться чего-то, не прерывая выполнение другого кода?

я должен написать функцию, которая, вызвав ее только один раз, должна:

  • включить выходной пин

  • контакт остается высоким в течение 200 мс

  • в конце таймера штифт должен снова стать низким.

  • контакт остается низким в течение 200 мс

  • по истечении таймера функция может быть вызвана снова.

для включения и выключения выходного контакта я уже написал и протестировал функции:

      outOn(pin_id);

outOff(pin_id);

теперь я пытаюсь написать функцию, которая выполняет вышеупомянутые действия, и вот что у меня получилось:

      void outOnT02(enum e_outs ou){
    
    outOn(ou);
    gu_RegTim.BTime[BTIM_FUNCT].Timer = O_SEC01*2; 
    
    if(gu_RegTim.BTime[BTIM_FUNCT].b.Stato == O_EndTimer) {
        outOff(ou);
    }
}

функция названаoutOnT02потому что:

  • это выход;
  • после его вызова булавка стала высокой;
  • T02, потому что контакт остается высоким в течение 0,2 секунды.

outOn(ou);заставляет булавку идти высоко,

outOff(ou);заставляет штифт идти низко,

gu_RegTim.BTime[BTIM_FUNCT].Timer = O_SEC01*2;запускает таймер на 200 мс,

и верно, когда таймер истек.

это работает, но, как вы понимаете, в противном случае я должен поставить его в циклgu_RegTim.BTime[BTIM_FUNCT].b.Stato == O_EndTimerникогда не будет правдой, и поэтому булавка останется высокой навсегда.

вот где я застрял. я не могу использоватьSLEEP(200);потому что я не могу прервать выполнение кода.

язык — C, ide — MPLAB X IDE v6.00, компилятор — XC8 v2.31, а процессор — PIC16F15355.

1 ответ

Этот пост немного устарел, но на него стоит ответить, поскольку это и хороший вопрос, и распространенная проблема. Теперь эта проблема очень распространена в мире встраиваемых систем, когда мы разрабатываем приложения, которые должны работать только на одном процессоре. Следовательно, в рабочем процессе нет реального параллелизма. Кроме того, поскольку приложение не будет работать поверх какой-либо ОС, не будет планировщика, таймеров, потоков и т. д. Особенно в микроконтроллерах небольшого масштаба нет возможности запуска многих настоящих ОСРВ.
Но это не должно быть препятствием для разработки приложений, выполняющих задачи одновременно. Мы можем разработать приложение, используя некоторые приемы, чтобы оно выполняло задачи одновременно и вело себя как небольшая ОС. Выполнение одновременно означает, что никакая задача не блокирует ЦП, используя проверки ожидания занятости или что-то подобное, но мы блокируем задачу, которая должна ожидать возникновения некоторого события.
Когда мы блокируем задачу, определенные данные и следующая точка выполнения этой задачи должны быть сохранены, чтобы она могла продолжиться с того места, где должна быть, при следующем выполнении. Знание того, что нам нужно сохранить, помогает нам создавать структуры, похожие на потоки, которые выполняются до тех пор, пока им не придется ждать какого-либо события (например, временной задержки). Когда он должен ждать (это означает, что он будет заблокирован), его следующее состояние должно быть сохранено, и он выходит, чтобы передать управление ЦП, чтобы он выполнял другие задачи.
Когда нам нужно иметь дело с периодическими задачами, как в вопросе, это относительно проще реализовать, не блокируя выполнение ЦП, и тем временем обрабатывать другие задачи. Кроме того, для задач этого типа не требуется использование прерываний, если только задачи не являются чрезвычайно чувствительными ко времени.
Ну, хватит с сюжетной частью, давайте приступим к делу. Я буду основывать примеры на проблеме перепрошивки вывода OP. Однако те же методы можно применять и для других ситуаций, таких как события ввода-вывода, аппаратные события и т. д.
Давайте кратко суммируем требования: у нас есть задача, которая выполняется атомарно. То есть, когда он вызывается, он должен выполняться до завершения, чтобы его можно было вызвать снова (это то, что я понимаю из требования ОП):

  • Включает выходной контакт на 200 мс
  • Затем отключает контакт на 200 мс.
  • После выключения и по истечении 200 мс его можно запустить снова.

Примечание. Некоторые функции в этом примере не реализованы, поскольку они могут зависеть от приложения или микроконтроллера.

Задачно-подобные функции

Предположим, мы хотим запланировать следующие две задачи, каждая из которых отслеживает точки продолжения выполнения.
Статические переменные объявлены в каждой функции, чтобы они помнили, с чего продолжать, всякий раз, когда они вызываются. Содержимое переменной не будет уничтожено компилятором при возврате функции, поскольку мы объявляем ее статической. необходимо обновлять при возникновении ожидаемых событий, чтобы перейти к следующему шагу всякий раз, когда он вызывается.
Обратите внимание, что в, источник вызова должен быть известен, чтобы управлять его атомарным поведением. Поскольку требование для этой задачи состоит в том, что после запуска или вызова она должна выполняться до завершения. Таким образом, мы должны знать, откуда вызывается задача, чтобы решить, что делать при каждом вызове. Если он был запущен из другой задачи, он больше не может быть запущен, пока не завершит процесс перепрошивки. Если он вызывается из планировщика (основной цикл), он знает, что это периодический вызов, и будет отслеживать время. Этот контроль достигается с помощью параметра, называемого. При вызове из планировщика этот параметр должен быть установлен в 1 и 0 для вызовов, отличных от планировщика.

      /*
 * This task-like function performs what the OP wants to achieve
 */
void outputTask(unsigned char periodic) {

    static unsigned char cp = 0; // Continuation Point holder
    static unsigned char currentMillis;
    
    /*
     * Check whether it is a periodic call or a new output signal call.
     * If it is a periodic call and signalling has been initialized,
     * proceed for time keeping.
     * If it is a new signalling call and the task hasn't completed yet,
     * simply ignore and return.
     */
     if(!periodic && cp != 0) {
        return;
     }
    
    switch(cp) {
        case 0:
            outOn(pin_id); // Turn on the output
            cp = 1; // Next execution point
            currentMillis = 200; // Load the 200ms counter for time keeping
            break;
            
        case 1:
            currentMillis--;
            if(currentMillis == 0) {
                // 200ms time for output high has elapsed, proceed to next step
                outOff(pin_id); // Turn off the output
                currentMillis = 200; // Reload the counter value
                cp = 2; // Proceed to the next step
            }
            break;
            
        case 2:
            currentMillis--;
            if(currentMillis == 0) {
                // 200ms time for output low has elapsed, proceed to next step
                cp = 0; // Last step is done, reset the state for new calls
            }
            break;
            
        default:
            // For anything else, reset the task state to the initials
            cp = 0 // Reset the task state to zero so that it accepts new calls
    }
}


/*
 * Let's say this task will wait for a button press event and will
 * trigger the outputTask upon the event occurs
 */
void outputTriggerTask() {

    static unsigned char cp = 0;
    static unsigned char currentMillis;
    
    switch(cp) {
        case 0:
            if(isButtonPressed()) { // Platform specific function
                // A button press has been detected, debounce first
                currentMillis = 50;
                cp = 1; // Next step, check for the elapsed time
            }
            else {
                break;
            }
            
        case 1:
            currentMillis--;
            if(currentMillis == 0) {
                // Check whether the button press is consistent
                if(isButtonPressed()) {
                    // Yes still consistent, handle the button press by triggering the output task
                    outputTask(0); // Not a periodic call
                    cp = 2; // Next step is to check whether button is released
                }
                else {
                    cp = 0; // Reset the task state
                }
            }
            break;
            
        case 2:
            if(isButtonReleased()) { // Platform specific function
                currentMillis = 50; // Reload the time counter
                cp = 3;
            }
            else {
                break;
            }
            
        case 3:
            currentMillis--;
            if(currentMillis == 0) {
                // Check whether the button release is consistent
                if(isButtonReleased()) {
                    // Yes still consistent, handle the button release if needed
                    cp = 0; // Reset the task to its initial state
                }
            }
            break;
            
        default:
            cp = 0; // Reset to initials
    }
}

Подходы к планированию

Следующие подходы предназначены для небольших встраиваемых систем, отличных от RTOS. Они подходят для широкого спектра 8-битных микроконтроллеров.

Подход 1 — создание временной базы на основе задержки для планирования задач

Планирование с использованием задержки блокировки ЦП подходит для хобби и образовательных целей, но не подходит для реальных проектов. В этом примере используется специфичная для платформыфункция (или может быть макросом) для создания такта 1 мс для приложения, чтобы задачи могли отслеживать время.

      void main(void) {
    
    systemInit(); // Platform specific function
    // maybe some more init functions go here
    
    // Application's infinite scheduler loop
    while(1) {
        // The first thing we do is to create a 1ms timebase using delay.
        // This is the heartbeat for the application
        delay_ms(1000); // Platform specific function
        
        // 1ms has elapsed check the tasks
        outputTriggerTask(); // Check whether any button press event has occured
        outputTask(1); // It is a periodic call for the output task
        // Maybe more tasks go here...
    }
}

Подход 2 — создание временной базы на основе аппаратного таймера

      void main(void) {
    
    systemInit(); // Platform specific function
    // Setup a hardware timer for 1ms overflow without interrupt
    initTimerForOneMs(); // Platform specific function
    // maybe some more init functions go here
    
    // Application's infinite scheduler loop
    while(1) {
        // Wait for the timer to overflow
        while(!isTimerOverflow()) // Platform specific function
            ;
        // Timer has overflowed, reload and check tasks
        reloadTimer(); // Platform specific function
        
        // 1ms has elapsed check the tasks
        outputTriggerTask(); // Check whether any button press event has occured
        outputTask(1); // It is a periodic call for the output task
        // Maybe more tasks go here...
    }
}

Подход 3. Перевод процессора в спящий режим на временную развертку 1 мс

      void main(void) {
    
    systemInit(); // Platform specific function
    // maybe some more init functions go here
    
    // Application's infinite scheduler loop
    while(1) {
        // Put the Processor to sleep along with a watchdog timer to wake it up
        clearWatchdogTimer(); // Platform specific function
        sleep(); // Platform specific function
        
        // CPU slept for 1ms and woke up, handle the periodic tasks
        outputTriggerTask(); // Check whether any button press event has occured
        clearWatchdogTimer(); // Platform specific function
        outputTask(1); // It is a periodic call for the output task
        clearWatchdogTimer(); // Platform specific function
        // Maybe more tasks go here...
    }
}

И последнее, но не менее важное: подход к проверке времени

В этом подходе задачи будут сохранять время, проверяя, лучше сказать, сравнивая прошедшее время с желаемым временем, чтобы отложить задачи, не блокируя ЦП. Для этого нам нужно будет использовать бесплатный таймер. Это будет похоже наФункция API Arduino.

Переписывание задач для подхода с проверкой времени

      /*
 * This task-like function performs what the OP wants to achieve
 */
void outputTask(unsigned char periodic) {

    static unsigned char cp = 0; // Continuation Point holder
    static unsigned short currentMillis; // 16 bit millisecond holder
    
    /*
     * Check whether it is a periodic call or a new output signal call.
     * If it is a periodic call and signalling has been initialized,
     * proceed for time keeping.
     * If it is a new signalling call and the task hasn't completed yet,
     * simply ignore and return.
     */
     if(!periodic && cp != 0) {
        return;
     }
    
    switch(cp) {
        case 0:
            outOn(pin_id); // Turn on the output
            cp = 1; // Next execution point
            currentMillis = getCurrentMillis(); // Platform specific function
            break;
            
        case 1:
            if(getCurrentMillis() - currentMillis >= 200) {
                // 200ms time for output high has elapsed, proceed to next step
                outOff(pin_id); // Turn off the output
                currentMillis = getCurrentMillis(); // Reload the counter value
                cp = 2; // Proceed to the next step
            }
            break;
            
        case 2:
            if(getCurrentMillis() - currentMillis >= 200) {
                // 200ms time for output low has elapsed, proceed to next step
                cp = 0; // Last step is done, reset the state for new calls
            }
            break;
            
        default:
            // For anything else, reset the task state to the initials
            cp = 0 // Reset the task state to zero so that it accepts new calls
    }
}


/*
 * Let's say this task will wait for a button press event and will
 * trigger the outputTask upon the event occurs
 */
void outputTriggerTask() {

    static unsigned char cp = 0;
    static unsigned short currentMillis;
    
    switch(cp) {
        case 0:
            if(isButtonPressed()) { // Platform specific function
                // A button press has been detected, debounce first
                currentMillis = getCurrentMillis(); // Platform specific function
                cp = 1; // Next step, check for the elapsed time
            }
            else {
                break;
            }
            
        case 1:
            if(getCurrentMillis() - currentMillis >= 50) {
                // Check whether the button press is consistent
                if(isButtonPressed()) {
                    // Yes still consistent, handle the button press by triggering the output task
                    outputTask(0); // Not a periodic call
                    cp = 2; // Next step is to check whether button is released
                }
                else {
                    cp = 0; // Reset the task state
                }
            }
            break;
            
        case 2:
            if(isButtonReleased()) { // Platform specific function
                currentMillis = getCurrentMillis();
                cp = 3;
            }
            else {
                break;
            }
            
        case 3:
            if(getCurrentMillis() - currentMillis >= 50) {
                // Check whether the button release is consistent
                if(isButtonReleased()) {
                    // Yes still consistent, handle the button release if needed
                    cp = 0; // Reset the task to its initial state
                }
            }
            break;
            
        default:
            cp = 0; // Reset to initials
    }
}

Планировщик для подхода к проверке времени

      void main(void) {
    
    systemInit(); // Platform specific function
    initMillisTimerWithInterrupt(); // Platform specific function
    // maybe some more init functions go here
    
    // Application's infinite scheduler loop
    while(1) {
        // Now that we use a free running millis timer no need to block the CPU to create a timebase
        
        // Just call tasks sequentially. Each task will know what to do individually
        outputTriggerTask(); // Check whether any button press event has occured
        outputTask(1); // It is a periodic call for the output task
        // Maybe more tasks go here...
    }
}
Другие вопросы по тегам