Код ISR +, интегрирующий код подсчета с подпрограммой ISR

У меня есть раздел кода C для устройства PIC, он активирует 4 отдельных реле по заранее определенному шаблону, каждое из которых устанавливается индивидуально, подсчитывая время и частоту появления. Этот паттерн продолжается до бесконечности, но обнаружил, что время на стандартном delay_ms недостаточно точное. Я хочу преобразовать его в процедуру ISR, я прилагаю ниже код, который зависит от стандартной задержки для вашего прочтения, моя главная задача заключается в том, как абстрагировать этот код в ISR, поскольку я знаю, что не следует вставлять весь этот код в фактическую процедуру ISR, любой совет очень ценен.

__CONFIG(FOSC_XT & WDTE_OFF & PWRTE_OFF & MCLRE_OFF & CP_OFF & CPD_OFF &
        BOREN_OFF & CLKOUTEN_OFF & IESO_OFF & FCMEN_OFF);

//!!! use BORV_HI for latest PICC compiler
__CONFIG(WRT_OFF & PLLEN_OFF & STVREN_OFF & LVP_OFF);       

#define _XTAL_FREQ  4000000

unsigned long int Sample_Period =20;
unsigned long int Sample_Duration = 2;
unsigned long int Sample_ON = 0;
unsigned long int WriteTX_Period = 21;
unsigned long int WriteTX_Duration = 1; //  
unsigned long int WriteTX_ON = 0;
unsigned long int Depass_Period = 60; //every 7 days for depass event
unsigned long int Depass_Duration = 10 ; // depass for 120 seconds
unsigned long int Depass_ON = 0 ;
unsigned long int Depass_Counter = 0;
unsigned long int Sample_Counter = 0;
unsigned long int WriteTX_Counter = 0;
unsigned long int count = 0;
unsigned char input;
char data = 1;

void SkipLine(void){
    printf("\n");
    printf("\r");
}

void main() {
    INTCON = 0;                 // disable interrupts.
    ANSELA = 0x00;              // all digital IO
    ANSELB = 0x00;              // all digital IO
    TRISA = 0b00000000;         // Configure PORTA as output 
    PORTA = 0b00000111;         // Initialize PORTA, all load relays are off.
    ADCON0 = 7;                 // disables ADC
    CM1CON0 = 7;                // Disable analog comparators
    TRISB = 0b10111001;         // all outputs bar RB1/RX pin, and RB7 (Prog'd)

    init_comms();

    printf("\n");
    printf("\r");
    printf("*******************************");
    printf("\n");
    printf("\r");
    printf("*METROL RELAY CONTROLLER MK1.0*");
    printf("\n");
    printf("\r");
    printf("*******************************");
    printf("\n");
    printf("\r");

    printf("Default timings are :");
    printf("\n");
    printf("\r");
    printf("Sample Period = ");
    printf("%d seconds", Sample_Period);
    printf("\n");
    printf("\r");

    printf("\n");
    printf("\r");
    printf("Sample Duration = ");
    printf("%d seconds", Sample_Duration);
    printf("\n");
    printf("\r");

    printf("\n");
    printf("\r");
    printf("WriteTX Period = ");
    printf("%d seconds", WriteTX_Period);
    printf("\n");
    printf("\r");

    printf("\n");
    printf("\r");
    printf("WriteTX Duration = ");
    printf("%d seconds", WriteTX_Duration);
    SkipLine;

    printf("\n");
    printf("\r");
    printf("Depassivation Period (Days)= ");
    printf("%d Days", Depass_Period);
    printf("\n");
    printf("\r");
    printf("\n");
    printf("\r");
    printf("Depassivation Duration  = ");
    printf("%d seconds", Depass_Duration);
    printf("\n");
    printf("\r");

    if (RB7 == 1)
    {
        printf("requires set up");

        printf("\n");
        printf("\r");
        printf("\n");
        printf("\r");

        printf("Enter value for Sample Period in minutes <0-255> ");
        printf("\n");
        printf("\r");

        char str[50];

        printf("Enter a string : ");
        gets(str);

        printf("You entered: %s", str);
        int SamplePeriodVal;
        SamplePeriodVal = atoi(str);
        printf("Sample Period Value entered = %d\n", SamplePeriodVal);
    }

    printf("\n");
    printf("\r");
    printf("system already configured");
    printf("\n");
    printf("\r");

    unsigned int Sample_Period_Units;
    Sample_Period_Units = EEPROM_READ(0x00);
    printf("sample value held in first eeprom address 0x00 is %d", EEPROM_READ(0x00));

    printf("\n");
    printf("\r");
    printf("load profile starting.....");
    printf("\n");
    printf("\r");

    while (1) {
        printf("\n");
        printf("\r");
        printf("test!");
        printf("\n");
        printf("\r");

        __delay_ms(990);

        if (Sample_Counter >= Sample_Period){
            PORTA = 0b00000110; //set Sample relay ON

            Sample_ON++;

            if (Sample_ON > Sample_Duration){
                Sample_ON = 0;
                Sample_Counter = 0;
                PORTA = 0b00000111;

            }
        }

        if (WriteTX_Counter >= WriteTX_Period){
            PORTA = 0b00000100; //set Write relay ON

            WriteTX_ON++;

            if (WriteTX_ON > WriteTX_Duration){
                WriteTX_ON = 0;
                WriteTX_Counter = 0;
                PORTA = 0b00000111;

            }
        }

        if (Depass_Counter >= Depass_Period){
            PORTA = 0b00000011; //set Depass relay ON


            Depass_ON++;

            if (Depass_ON > Depass_Duration){
                Depass_ON = 0;
                Depass_Counter = 0;
                PORTA = 0b00000111;

            }
        }

        Sample_Counter++;
        WriteTX_Counter++;
        Depass_Counter++;
        count++;            // increment total count for system

        printf("\n");
        printf("\r");

        int SampleAct;
        SampleAct = RB3;
        printf("Port B sample value =%d  ", SampleAct);

        printf("\r");
        printf("\n");

        int WriteTXAct;
        WriteTXAct = RB4;
        printf("Port B WriteTX value =%d  ", WriteTXAct);

        printf("\r");
        printf("\n");

        int DepassAct;
        DepassAct = RB5;
        printf("Port B Depass value =%d  ", DepassAct);

        printf("\r");
        printf("\n");
        printf("%ld", count);

        int PortB_Val;

        PortB_Val = PORTB & 0b00111000;

        switch (PortB_Val)
        {

        case 0x28:
            RB6 = RB6;
            printf("\n");
            printf("\r");
            printf("Sample+Depass error");
            break;

        case 0x30:
            RB6 = RB6;
            printf("\n");
            printf("\r");
            printf("Write+Depass error");
            break;

        case 0x38:
            RB6 = RB6;
            printf("\n");
            printf("\r");
            printf("Write+Sample+Depass error");
            break;

        default:
            RB6 = !RB6;
        }
    }
}

person StevieB    schedule 21.08.2015    source источник
comment
?? как только вы вытащите отладочные printf и т. д., единственным «настоящим» кодом будет чтение/запись некоторого порта. Почему это не может быть в обработчике прерывания? Эти данные управления/состояния со счетчиком и т. д. действительно должны быть в одной структуре «TimerControlBlock», но это хорошая практика, а не существенная.   -  person Martin James    schedule 21.08.2015
comment
Привет, Мартин, я хочу сохранить команды printf для проверки работоспособности и даже записи в последовательный файл для проверки системы, но я обеспокоен тем, что этот тип команды не относится к циклу ISR. Могу ли я просто увеличить все счетчики в ISR, а затем сделать мой printf снаружи? я измерил время выполнения этого кода и его 97 мс, но является ли этот подход ошибочным?   -  person StevieB    schedule 21.08.2015
comment
... но является ли этот подход ошибочным? Нет, он сломан. 1) вообще нонсенс иметь здесь такую ​​кучу printf`ов. 2) нонсенс иметь тогда с функциональным кодом. 3) оставлять их после тестирования — это просто ерунда. Как профессионал, вы бы играли в азартные игры со своей работой.   -  person too honest for this site    schedule 21.08.2015
comment
И delay_все, что является нестандартным.   -  person too honest for this site    schedule 21.08.2015
comment
Какое ПОС-устройство? Какой PIC-компилятор?   -  person Clifford    schedule 22.08.2015
comment
Эта строка: 'char data = 1;' вставляет целое число в char. Лучший способ написать это: 'char data = '1'. Опубликованный способ приводит к данным, содержащим 'x01'. Лучший способ приводит к данным, содержащим фактический символ: '0x31'   -  person user3629249    schedule 22.08.2015
comment
@user3629249 user3629249: В этом коде можно многое критиковать, но я бы не стал начинать с этого. Поскольку data не упоминается в коде, мы не можем сказать, для чего он используется, поэтому ваша критика необоснованна. Несмотря на свое название, в C char — это просто целочисленный тип, и его не нужно использовать исключительно для представления отображаемого символа. Более того, 1 или ASCII SOH в любом случае являются вполне допустимым управляющим символом и могут быть преднамеренными. Меня больше беспокоит использование глобальные данные в первую очередь.   -  person Clifford    schedule 22.08.2015
comment
Вы знаете, что этот код был бы гораздо более кратким (и эффективным), если бы вы включили концы строк в выходные данные; например: printf( "Port B sample value =%d \r\n", SampleAct ) ; Вывод каждого в отдельных вызовах printf() несколько странен, если не сказать больше.   -  person Clifford    schedule 22.08.2015
comment
Я предлагаю вам выбрать одно соглашение относительно того, где разместить {, и придерживаться его!   -  person Clifford    schedule 22.08.2015


Ответы (2)


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

volatile static uint8_t tick;

ISR(TIMER_vec) /* whatever int vector is triggered by your timer */
{
    ++tick;
}


int main () {
    /* [...] */

    while (1)
    {
        uint8_t lasttick = 0;
        while (tick != lasttick)
        {
            lasttick = tick; /* or ++lasttick; for handling "missed" interrupts late */

            /*
             * do your periodic stuff here
             */
        }
        /* wait for next interrupt, e.g. by entering sleep state
           for AVR: */
        sleep_cpu();
    }
}

Конечно, вы должны организовать прерывание таймера, в зависимости от вашего чипа. Если ситуация усложнится, вам может быть интересен код, который я написал использование ISR для заполнения очереди событий.

person Community    schedule 21.08.2015
comment
lasttick должен быть инициализирован тиком, а не нулем. - person Lundin; 21.08.2015
comment
@Lundin, это то же самое, потому что tick имеет статическое хранилище (неявно инициализированное нулем) - person ; 21.08.2015
comment
Нет, потому что с момента инициализации таймера до момента выполнения кода прошло переменное количество времени. Кроме того, во встраиваемых системах очень часто не реализуется стандартная статическая инициализация, и в этом случае она даже не нужна, поскольку фактическое значение tick не имеет значения для работы программы. - person Lundin; 21.08.2015
comment
@Lundin: 1. avr-libc реализует его, если он НЕ реализован, вы ВСЕ ЕЩЕ можете инициализировать его значением 0, 2. обычно вы настраиваете свои прерывания последними перед входом в цикл событий, 3. использование 0 здесь позволяет создать дизайн, в котором тикает все равно должно обрабатываться поздно (дизайнерское решение, в зависимости от ваших потребностей). Итак, просто оставьте подробности читателю, чтобы представить общую идею. Возможную релевантность фактического значения tick см. в комментарии во внутреннем цикле. - person ; 21.08.2015
comment
Я вижу плохое состояние гонки: что, если прерывание сработает в sleep_cpu? Хорошее приложение для stdatomic. - person too honest for this site; 21.08.2015
comment
@ Олаф, надо проверить. Проблема может возникнуть, когда прерывание обслуживается непосредственно перед инструкцией сна. Проще всего здесь не пытаться заснуть, но это может быть расточительно. - person ; 21.08.2015
comment
Проблема в том, что тест и обновление tick не являются атомарными. Также установка lasttick на ноль сомнительна. В любом случае, несколько портов можно было бы проще установить в ISR, когда основной цикл просто просыпается, чтобы обновить статус пользователя (например, эти малоиспользуемые printf). Это также значительно уменьшит джиттер. - person too honest for this site; 21.08.2015
comment
@Olaf, это проблема только в том случае, если вы беспокоитесь о пропуске прерывания, а не в этой базовой схеме, но может быть. С другой стороны, проблема со сном, хотя и маловероятная, довольно серьезная, поскольку приходится ждать следующего прерывания, хотя одно ожидается. Мне придется разобраться с этим, простая атомарная операция здесь не поможет. - person ; 21.08.2015
comment
@Olaf версия для того, чтобы не пропустить ЛЮБОЕ прерывание, находится в комментарии, и нет гонки без каких-либо атомов ... это очень похоже на мой реальный код. Настоящая проблема с этим решением действительно sleep... Я еще раз сверюсь со спецификациями, чтобы узнать, есть ли решение. - person ; 21.08.2015
comment
@Olaf, и действительно, при установке флага прерывания на AVR прерываниям разрешена только одна инструкция позже, поэтому подсказка здесь будет cli(); if (tick == lasttick) { sei(); cpu_sleep(); } else sei(); - я обновлю свой реальный код, спасибо за подсказка, но не этот ответ, потому что я чувствую, что это слишком специфично для машины... - person ; 21.08.2015
comment
@FelixPalmen: я бы назвал прерывание, проходящее незамеченным, очень хорошо ошибкой. Для ссылки: я не принимаю внешние ссылки как часть ответа. В любом случае, было бы намного проще, если бы вы просто подождали, пока tick не изменится с lasttick. Затем код будет выполняться во внешнем цикле (внутренний цикл пуст). Для этого потребуется еще одна временная переменная для цикла ожидания. Другие мои проблемы (джиттер и т. д.) все еще могут быть актуальны. - person too honest for this site; 21.08.2015
comment
@FelixPalmen: я не углублялся в PIC (рад, что мне пришлось использовать его только много лет назад). Тем не менее, это может быть лучший ассемблерный код, чтобы гарантировать, что компилятор не будет вмешиваться в последовательность. - person too honest for this site; 21.08.2015
comment
@Olaf - как я уже сказал, я привел здесь пример для AVR (не зная PIC), и avr-gcc гарантирует, что вы выдаете просто инструкцию sei для sei() и просто sleep вместо cpu_sleep(), так что будет работать как положено. На все остальное не стесняйтесь давать другой ответ. Многое зависит от того, что вы на самом деле хотите сделать, и я принял во внимание только то, что OP не хочет помещать весь свой код в ISR. - person ; 21.08.2015
comment
Хм, я не нашел этого в ответе. Мог перечитать комментарий - извините. Впрочем, я и AVR не использую. Просто меньшее дерьмо MCU с архитектурой 80-х / начала 90-х годов. - person too honest for this site; 21.08.2015
comment
Что касается инициализации lasttick, мой совет никогда не навредит, но в некоторых случаях приведет к более надежному коду. Даже если вы разрешите прерывания непосредственно перед циклом, у вас будет задержка [настройка таймера] + [накладные расходы цикла] + [время инициализации lasttick] + [накладные расходы на вызов ISR], а не только [накладные расходы на вызов ISR]. Имейте в виду, что это PIC, самый медленный кусок дерьма 80-х, который все еще производится. - person Lundin; 21.08.2015
comment
@Lundin, это зависит от того, ищете ли вы точное время или обрабатываете каждое прерывание, независимо от того, когда. Инициализация lasttick в tick будет способствовать первому. Так что я по-прежнему объявляю это проектным решением (которое следует принимать, зная последствия). Вероятно, это выходит за рамки этого набросанного ответа. - person ; 21.08.2015
comment
@Olaf Что заставляет вас думать, что tick не является атомарным? Вам придется разобрать код, чтобы сказать, он будет сильно зависеть от ядра процессора и компилятора. ++tick; внутри ISR никогда не может быть проблемой, потому что при выполнении ISR основная программа не будет прервана. Проблемы могут возникнуть только при чтении tick в основном цикле. В этом случае main следует переписать как while ((tmp=tick) != lasttick) { lasttick = tmp;. Я почти уверен, что чтение tick будет атомарным на большинстве микроконтроллеров. - person Lundin; 21.08.2015
comment
@FelixPalmen Я рассматриваю случай, когда таймер уже работает так долго, что lasttick оказывается равным tick в момент, когда условие цикла выполняется впервые. Это может быть вообще не проблема, тем не менее lasttick = tick; поскольку инициализация не может причинить никакого вреда. - person Lundin; 21.08.2015
comment
@Lundin: вся последовательность while (tick != lasttick) { lasttick = tick; не является атомарной, но я бы сказал, что это не имеет значения, потому что это разновидность кода для случаев, когда лучше пропустить галочку, чем обрабатывать ее с опозданием. Альтернатива находится в комментарии (++lasttick) и не требует атомарной операции для правильной работы. - person ; 21.08.2015
comment
В качестве альтернативы, я полагаю, можно просто выбросить оба счетчика из файловой области, сравнить их внутри ISR, а затем установить логический флаг, когда сравнение сработает. Должен решить все проблемы со временем и повторным входом. - person Lundin; 21.08.2015
comment
@Lundin логический флаг будет работать только когда вы хотите отбрасывать тики (более одной), что может произойти, пока вы все еще обрабатываете последнюю. Опять же, это зависит от варианта использования... - person ; 21.08.2015

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

Ваша проблема скорее в вашем дизайне и использовании printf(). Если printf() не буферизуется или вы заполняете буфер, то время цикла будет определяться выводом отладки - если вывод printf() осуществляется через последовательный порт UART, время цикла будет определяться скоростью передачи данных этот канал и объем вывода текста.

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

Лучшим методом будет опрос таймера и выполнение тела цикла, когда наступит время, а не после фиксированной задержки. Реализация таймера зависит от платформы, но если вы реализуете таймер, ISR которого увеличивает счетчик каждую миллисекунду, а счетчик считывается функцией gettime_ms(), тогда ваш цикл становится таким:

int start_1000 = gettime_ms() ;
int now = start ;
for(;;)
{
    now = gettime_ms() ;

    if( now - start_1000 >= 1000 )
    {
        start_1000 += 1000 ;

        //  Loop body here - will execute every 1000ms
        // so long as the loop body takes less than 1000ms in total.
        ...
    }
}

Затем вы можете легко ввести другие периодические операции с разными скоростями:

int now = gettime_ms() ;
int start_1000 = now;
int start_50 = now ;

for(;;)
{
    now = gettime_ms() ;

    // Every second
    if( now - start_1000 >= 1000 )
    {
        start_1000 += 1000 ;

        // 1 second operations here
        ...
    }

    // Every 50ms
    if( now - start_50 >= 50)
    {
        start_50 += 50 ;

        // 50ms operations here 
        ...
    }
}

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

В конечном счете, если необходимо выполнить значительно критичные по времени задачи и обработку событий, то ОСРВ может подойти.

person Clifford    schedule 22.08.2015
comment
@olaf выходные данные sprintfs используются для регистрации данных для постобработки, их отсутствие действительно вызовет проблемы с моей работой, есть поток данных, поступающий от оборудования National Instruments, и последовательные данные являются основой для наших результатов, sprintf остается, ,,,,,, спасибо за весь вклад, я исправлю свои коды, большое спасибо, господа... - person StevieB; 24.08.2015
comment
основной цикл в моем коде занимает менее 100 мс, поэтому я поместил это в приведенную в порядок кодовую процедуру ISR, спасибо всем,,,, - person StevieB; 24.08.2015
comment
@StevieB: Как правило, 100 мс слишком долго для кода ISR. Прерывания с более низким приоритетом будут задержаны и станут недетерминированными. - person Clifford; 25.08.2015