Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Обработчик прерываний в HI-TECH
Форум разработчиков электроники ELECTRONIX.ru > Микроконтроллеры (MCs) > Все остальные микроконтроллеры > PIC
Grigorij
Здравствуйте.

Наконец-таки начал переползать с ассемблера на Си и возникла следующая проблем. Имеется код (приведены только отрывки на которых спотыкается компилятор):

Код
//изменение скорости обмена
void SetBRate(unsigned brate)
{
....
}

//отправка данных
void SendByte(unsigned brate,unsigned char data)
{    
     SetBRate(brate);
....
}

//отправка данных на индикатор
static void _send_byte(unsigned char data)
{
....
    SendByte(br_19200,data);    
....
    SetBRate(br_1200);
}    

//вывод символа на индикатор
void WriteChar(unsigned char chr)
{
     //корректировка кода для русских букв
     if (chr >= 0xC0 && chr <= 0xEF)
         chr -= 0x40;
     else
         if (chr >= 0xF0)
            chr -= 0x10;
    
     _send_byte(chr);
}

//обработчик прерываний от Timer 0    
void INT_TMR0(void)
{    
     static int cnt = 0;
    
      if (++cnt >= 10)
      {
           WriteChar('1');
           cnt = 0;
       }
    
       T0IF = 0;    
}

//обработчик прерываний
void interrupt INTHandler(void)
{
      if (T0IF) INT_TMR0();    //прерывание от Timer 0
      if (RCIF) INT_RxD();    //прерывание от приемника EUSART
}


При комплияции выдаются следующие сообщения об ошибках:

Код
Error   [472]; . non-reentrant function "__send_byte" appears in multiple call graphs: rooted at "_INTHandler" and "_main"

Error   [472]; . non-reentrant function "_SendByte" appears in multiple call graphs: rooted at "_INTHandler" and "_main"

Error   [472]; . non-reentrant function "_SetBRate" appears in multiple call graphs: rooted at "_INTHandler" and "_main"


В документации на HI-TECH написано, что:

(472) non-reentrant function "*" appears in multiple call graphs: rooted at "*" and "*" (Linker)
This function can be called from both main-line code and interrupt code. Use the reentrant keyword, if this compiler supports it, or recode to avoid using local variables or parameters, or duplicate the function, e.g.:


Однако из описания ошибки я так и не понял, как ее исправить. Подскажить, на какие грабли я наступил?

P.S. Используется MPLAB 8.00 + HI-TECH 9.60 LITE (который идет вместе с MPLAB 8.00). Контроллер PIC16F690.
Vanizma
ты используешь одну функцию и в основном теле программы и в прерывании. Нельзя так. И вообще лучше не использовать функции в прерываниях. У меня использовалась, так при нагреве устройства до 54 град. прога глючить начинала. заменил самописным кодом - все нормально стало.
evc
Цитата(Grigorij @ May 7 2008, 09:00) *
...
(472) non-reentrant function "*" appears in multiple call graphs: rooted at "*" and "*" (Linker)
This function can be called from both main-line code and interrupt code. Use the reentrant keyword, if this compiler supports it, or recode to avoid using local variables or parameters, or duplicate the function, e.g.:


Однако из описания ошибки я так и не понял, как ее исправить. Подскажить, на какие грабли я наступил?

P.S. Используется MPLAB 8.00 + HI-TECH 9.60 LITE (который идет вместе с MPLAB 8.00). Контроллер PIC16F690.


Компилер вам говорит: "Это НЕ функция которая позволяет многократный вход в нее, а она вызываемая в нескольких мест "*" и "*". Эту функцию возможно вызывать из обеих "main" и "interrupt". Пользуйте ключевое слово "reentrant", если этот компилер его поддерживает, или перепишите ее, чтобы она не использовала локальные переменные или продублируйте ее."

Дело в том что вы пользуете эту функцию и в основной программе и в обработчике прерывании. Задумайтесь что случится с данными, если прерывание наступит во время исполнении именно этой функции и обработчик прерывания попытается тоже вызвать ее!
В прерывании лучше внешние функции вообще не применять.
DL36
Цитата(evc @ May 7 2008, 08:35) *
Компилер вам говорит: "Это НЕ функция которая позволяет многократный вход в нее, а она вызываемая в нескольких мест "*" и "*". Эту функцию возможно вызывать из обеих "main" и "interrupt". Пользуйте ключевое слово "reentrant", если этот компилер его поддерживает, или перепишите ее, чтобы она не использовала локальные переменные или продублируйте ее."

Ключевое слово #pragma interrupt_level 0
Цитата
Дело в том что вы пользуете эту функцию и в основной программе и в обработчике прерывании. Задумайтесь что случится с данными, если прерывание наступит во время исполнении именно этой функции и обработчик прерывания попытается тоже вызвать ее!
В прерывании лучше внешние функции вообще не применять.
Сергей Борщ
Цитата(Grigorij @ May 7 2008, 08:00) *
or recode to avoid using local variables or parameters
Нет у PIC16 стека, на котором компилятор может сохранить временные переменные. И нет развитой косвенной адресации.
evc
Цитата(DL36 @ May 7 2008, 09:56) *
Ключевое слово #pragma interrupt_level 0


это слово указывает линкеру, что потребитель гарантирует и береть на себя ответственость, что эта функция не будет вызвана обработчиком прерывании и основной программы одновременно, что бы тот (линкер) не выдавал сообщении об ошибке. В некоторых компиляторах (поддерживающие рекурсию, например) есть возможность объявить функцию как "reentrant", т.е. она может быть вызвана еще раз не завершив свою работу.
DL36
Цитата(evc @ May 7 2008, 10:12) *
это слово указывает линкеру, что потребитель гарантирует и береть на себя ответственость, что эта функция не будет вызвана обработчиком прерывании и основной программы одновременно, что бы тот (линкер) не выдавал сообщении об ошибке. В некоторых компиляторах (поддерживающие рекурсию, например) есть возможность объявить функцию как "reentrant", т.е. она может быть вызвана еще раз не завершив свою работу.
Все правильно, также правильно не использовать функции в прерываниях.
Сергей Борщ
Цитата(DL36 @ May 7 2008, 11:54) *
Все правильно, также правильно не использовать функции в прерываниях.
Глупости. Если надо сделать однотипное логически завершенное действие несколько раз, то для улучшения читаемости программы и уменьшения вероятности ошибки в общем случае можно и нужно такое действие оформлять в виде функции. Даже если общих кусков кода и нет, вынесение в функции логически завершенных кусков кода очень способствует читаемости программы. Другое дело, что недостаточно хороший компилятор может наложить свои ограничения, но это не повод заявлять "не использовать вообще".
Vanizma
Цитата(Сергей Борщ @ May 7 2008, 14:13) *
Если надо сделать однотипное логически завершенное действие несколько раз,


для этого можно и макросы использовать.
DL36
Цитата(Сергей Борщ @ May 7 2008, 13:13) *
Глупости. Если надо сделать однотипное логически завершенное действие несколько раз, то для улучшения читаемости программы и уменьшения вероятности ошибки в общем случае можно и нужно такое действие оформлять в виде функции. Даже если общих кусков кода и нет, вынесение в функции логически завершенных кусков кода очень способствует читаемости программы. Другое дело, что недостаточно хороший компилятор может наложить свои ограничения, но это не повод заявлять "не использовать вообще".
Мое замечание относилось, только к увеличении времени сохранения,восстановления контекста.
При использовании одной функции в основном коде и прерываниях Хайтек сохраняет наверно все используемые переменные, а это здорово увеличивает время обработки.

Касательно выделения логических блоков в функцию совершенно согласен.
Grigorij
Цитата(evc @ May 7 2008, 11:12) *
это слово указывает линкеру, что потребитель гарантирует и береть на себя ответственость, что эта функция не будет вызвана обработчиком прерывании и основной программы одновременно, что бы тот (линкер) не выдавал сообщении об ошибке. В некоторых компиляторах (поддерживающие рекурсию, например) есть возможность объявить функцию как "reentrant", т.е. она может быть вызвана еще раз не завершив свою работу.


К сожалению ключевое слово #pragma interrupt_level 0 не помогло. Если я объявляю ф-цию следующим образом:

Код
#pragma interrupt_level 0
void SendByte(unsigned brate,unsigned char data)
{    
     SetBRate(brate);
     TXREG = data;
     while (!TRMT);
}


То получаю от компилятор сообщение:

Код
Error   [473]; . function "_SendByte" is not called from specified interrupt_level


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

Код
void main(void)
{        
    Init();
    
    while (1)
    {
         if (T0IF) INT_TMR0();
         if (RCIF) INT_RxD();
    }
}


Решение, наверное, не слишком красивое, но пока сойдет. Как вариант, думаю продублировать некоторые ф-ции, которые используются как в обработчике прерываний, так и в основной программе, т.е. сделать что-то на подобии:

Код
void intr_SetBRate(unsigned brate)
{
....
}

void intr_SendByte(unsigned brate,unsigned char data)
{    
     intr_SetBRate(brate);
     TXREG = data;
     while (!TRMT);
}


Но сдерживат тот факт, что произойдет увеличит объем кода, а это не есть хорошо. Тоже каксается и использования макросов.

Большое спасибо всем за помощь.
DL36
Цитата(Grigorij @ May 8 2008, 07:24) *
К сожалению ключевое слово #pragma interrupt_level 0 не помогло. Если я объявляю ф-цию следующим образом:


Надо добавить такую же строчку перед обработчиком прерывания. Хотя в Вашем случае видимо оптимальнее сделать две одинаковые функции с разными именами.

Надо учиться использовать прерывания, это основа без нее никак.
XVR
Цитата(Grigorij @ May 7 2008, 09:00) *
Здравствуйте.

Наконец-таки начал переползать с ассемблера на Си и возникла следующая проблем. Имеется код
При комплияции выдаются следующие сообщения об ошибках:

Код
Error   [472]; . non-reentrant function "__send_byte" appears in multiple call graphs: rooted at "_INTHandler" and "_main"

Error   [472]; . non-reentrant function "_SendByte" appears in multiple call graphs: rooted at "_INTHandler" and "_main"

Error   [472]; . non-reentrant function "_SetBRate" appears in multiple call graphs: rooted at "_INTHandler" and "_main"
Может я ошибаюсь, но по сообщениям об ошибках у меня сложилось впечатление, что вы пытаетесь вызвать функции SendByte и SetBRate из прерывания и из основной программы. Так же, судя по именам функций, они напрямую работают с UART'ом.

Если я прав, то это все будет работать до тех пор, пока основная программа и прерывание не попытаются ОДНОВРЕМЕННО отправить байт в UART. А они попытаются, т.к. передача байта (если он передается не на 10 MHz smile.gif ) вещь довольно длинная, вероятность попасть в нее относительно велика.



Цитата
Подскажить, на какие грабли я наступил?
Пока только на компиляторные, следующие грабли будут называться 'работа с одним физическим ресурсом из параллельных потоков вычислений без должной синхронизации'
Grigorij
Цитата(DL36 @ May 8 2008, 09:19) *
Надо добавить такую же строчку перед обработчиком прерывания. Хотя в Вашем случае видимо оптимальнее сделать две одинаковые функции с разными именами.

Спасибо, действительно помогло добавление #pragma interrupt_level 0 перед обработчиком прерываний. Хотя я думаю в обработчик прерываний вынести обработку только наиболее значимых прерываний, а все остальные обработать так, как я писал выше (т.е. в основном цикле программы). Думаю должно получиться неплохо.

Хотя вариант с использованием двух одинаковых функции с разными именами я пока еще не отбросил.

Цитата(DL36 @ May 8 2008, 09:19) *
Надо учиться использовать прерывания, это основа без нее никак.

Знаю, что основа. Поэтому и задал вопрос, чтобы понять как пользоваться прерываниями в HI-TECH, т.к. в asm-е у меня вопросов с ними вроде не возникало.
DL36
Цитата(Grigorij @ May 8 2008, 09:59) *
Спасибо, действительно помогло добавление #pragma interrupt_level 0 перед обработчиком прерываний. Хотя я думаю в обработчик прерываний вынести обработку только наиболее значимых прерываний, а все остальные обработать так, как я писал выше (т.е. в основном цикле программы). Думаю должно получиться неплохо.

Хотя вариант с использованием двух одинаковых функции с разными именами я пока еще не отбросил.
Знаю, что основа. Поэтому и задал вопрос, чтобы понять как пользоваться прерываниями в HI-TECH, т.к. в asm-е у меня вопросов с ними вроде не возникало.
Старайтесь в прерывании производить только самые необходимые действия. Например принять байт по уарт, положить его в буфер, поднять флаг что надо что то сделать и уйти из прерывания, дабы не потерять другие вызовы.

Попробуйте описать тут концептуально свою задачу, думаю народ поможет, поскольку у Вас где то ошибка в концепции построения программы.
Галстук
Цитата(DL36 @ May 8 2008, 13:24) *
...у Вас где то ошибка в концепции построения программы.

а может концепция верная.

В PICC-18 User Guide, 4.11.4 Interrupt Levels рассматривается ситуация использования одной и той же функции из interrupt и из main-line code. Для этого случая специально предназначена #pragma interrupt_level 0, возможно еще значение 1. Компилятор после этого ругаться перестает. Считается, что пользователь для всех функций, которым приписан один уровень (для чего надо написать эту волшебную фразу перед каждой из них), обеспечит правильную работу - т.е. они не будут вызваны одновременно из interrupt и main-line. Напрмер, перед вызовом функции из main-line можно запретить прерывания (и разрешить прерывания сразу после возврата из нее).

этим достигается компактность кода и лучшая читаемость. однако временный запрет прерывания чреват его пропуском, так что должен быть анализ в каждом случае.
Grigorij
2DL36:

Постараюсь в кратце описать программу. Она предназначена для считывания данных с датчика, их анализа и вывода на индикатор необходимой информации. При этом данные также архивируют и заносятся в EEPROM. Программа разделена на 2 части. Первая - работа с периферийными устройствами (индикатор, интерфейс RS-232/485, RTC, EEPROM, клавиатура и пр.). Вторая - это математика (пока еще математический аппарат не описан, но скорее всего будет простейший алгоритм апроксимации полученных данных и вывод графика на индикатор).

Сейчас я сделал следующим образом (только часть, чтобы описать концепцию программы):

1) в основной программе я проверяю:

а) флаг прерывания от таймер (он мне нужен только для опроса кнопок), которые выставляется не
чаще 3-х раз в секунду
б) ряд своиг флагов (как например, готовность данных к обработке)

2) в обработчике прерываний я:

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

Одним словом, так как Вы и описали.

В целом же меня больше интересовал вопрос о вызове как в обработчике прерываний, так и в основной программе одних и тех же ф-ций. Как я и писал, проблема решалась использованием #pragma interrupt_level 0.


2XVR:

К счастью, описанные Вами грабли по работе с одним физическим ресурсом из параллельных поток вычислений были успешно преодалены введеним переменной-семафора, которая в нужный момент запрещает или разрешает доспут с данным.
DL36
Цитата(Grigorij @ May 8 2008, 14:06) *
В целом же меня больше интересовал вопрос о вызове как в обработчике прерываний, так и в основной программе одних и тех же ф-ций. Как я и писал, проблема решалась использованием #pragma interrupt_level 0.

Вообще применение #pragma interrupt_level это исключительный случай, я так считаю.

Попробуйте в симуляторе программно поднять какой нибуть флаг и посмотрите сколько тактов пройдет от момента поднятия флага до начала обработки прерывания. Если это время Вас устраивает то проблем нет. Кстати именно столько времени потребуется для выхода из прерывания. В случае одновременного применения функции в прерывания и основном цикле, это время бывает достаточно значительным и уменьшение размера кода это не окупает.
asmon
А кто может обьяснить назначение директивы хайтека #pragma inline func_name ? 07.gif
По логике С++ функци с этой директивой должны быть подобны макросам.
И тогда обсуждаемая проблема отпадёт сама собой (и вырастет код smile.gif ). Но как - то непонятно эта директива описана в даташите и в своё время я с ней не разобрался... wassat.gif
xemul
Цитата(asmon @ May 22 2008, 18:22) *
А кто может обьяснить назначение директивы хайтека #pragma inline func_name ? 07.gif
По логике С++ функци с этой директивой должны быть подобны макросам.
И тогда обсуждаемая проблема отпадёт сама собой (и вырастет код smile.gif ). Но как - то непонятно эта директива описана в даташите и в своё время я с ней не разобрался... wassat.gif

И не разбирайтесь - для мелких пиков не актуально. Из мануала к писс 9.60:
Цитата
The #pragma inline directive is used to indicate to the compiler that a function is to be inlined.
The directive is only able to be used on functions that are hard coded in the code generator of the
compiler. User defined and library function are not able to be inlinded.


2Grigorij: а зачем Вам что-то передавать не в прерывании? Положили все, что нужно, (через Ваш send_byte() или как-нибудь еще) в (кольцевой) буфер передачи, установили TXIE = 1, и контроллер, если не занят передачей, сразу попадает в прерывание УСАРТа. В прерывании кидаете очередной байт из буфера в TXREG. Когда все данные будут переданы, останется только сделать TXIE = 0. Нужда в дублировании функций и прочем геморе при этом не возникает. (кста, прошная версия писс сама сделает дубль функции, используемой и в прерываниях, и вне их).
И дергать конфиг УСАРТа перед каждым байтом совершенно бессмысленно - один раз настроились (можно и автободом, если он нормально работает, т.е. сначала читаем ерраты на используемый кристалл) и забыли.
Grigorij
2xemul

Рассказываю. В моей программе USART используется для 2-х целей: управление индикатором GP1184A01A и связь с датчиком давления и температуры по RS-485. Скорость общения с датчиком 1200 бод (перенастроить нельзя никак). Минимальная скорость, на которой можно общаться с индикатором 9600 бод. В следствие все этого приходится время от времени перенастраивать USART. Сейчас я "минимизировал" кол-во перенастроек, т. к. всегда приходится отправлять не менее 4-х байт (т.е. не приходится "И дергать конфиг УСАРТа перед каждым байтом"). Если бы скорость была все время одна и таже проблем бы не было, то действительно "один раз настроились и забыли".

Что касается вызова ф-ций. У меня в основной ф-ции (то бишь в main) происходит настройка индикатора и для этого, естественно, используется ф-ция SendByte. При возникновении прерывания от таймера (с помощю таймера сделан RTC) необходимо отослать запрос на датчик с целью получения от него данных, опять же используется ф-ция SendByte. В итоге компилятор начинал ругаться. Конечно, можно и просто все складывать в буфер, как Вы и говорите, но, на мой взгля в программе потом очень легко будет запутаться.
xemul
Цитата(Grigorij @ May 23 2008, 08:45) *
2xemul

Рассказываю. В моей программе USART используется для 2-х целей: управление индикатором GP1184A01A и связь с датчиком давления и температуры по RS-485...

Заведите два буфера передачи. Если понадобится передавать на 3-ей скорости, добавите еще один. И т.д. В send_byte() в качестве параметра можно передавать указатель на требуемый буфер, или использовать персональный send_byte() для каждого буфера. Но вся физическая работа с УСАРТом все равно будет происходить только в прерывании. Наверху (в send_byte()) Вы только заполняете буферы и разрешаете передачу.
Grigorij
Цитата(xemul @ May 23 2008, 12:57) *
Заведите два буфера передачи. Если понадобится передавать на 3-ей скорости, добавите еще один. И т.д. В send_byte() в качестве параметра можно передавать указатель на требуемый буфер, или использовать персональный send_byte() для каждого буфера. Но вся физическая работа с УСАРТом все равно будет происходить только в прерывании. Наверху (в send_byte()) Вы только заполняете буферы и разрешаете передачу.


Наверное мы друг друга не понимаем smile.gif.

Сейчас для работы с USART-ом у меня используются следующие ф-ции:
Код
//изменение скорости обмена
void SetBRate(unsigned brate)
{
   SPEN = 0;    
   SPBRGH    = (brate >> 8);
   SPBRG = (brate & 0x00FF);
   SPEN = 1;
   CREN = 1;
   TXEN = 1;
}    

//отправка данных
void SendByteUART(unsigned char data)
{
    CREN = 0;
    TXREG = data;
    while (!TRMT);
    CREN = 1;
}

Когда мне надо передать команду, например, на датчик, я переключаю скорость обмена USART на 1200 и отправляю небходимое кол-во байт. Аналогично делаю и для индикатора (только скорость 19200). Никаких здесь проблем не возникает. Но ф-цию SendByteUART мне приходилось (сейчас концепция слегка переработана) вызывать, как в ф-ции main, так и в обработчике прерываний (и никуда деться нельзя). В итоге компилятор на это ругался (конечно если не использовать #pragma interrupt_level), причем абсолютно не важно, что делает ф-ция SendByteUART.
xemul
Цитата(Grigorij @ May 23 2008, 14:54) *
Наверное мы друг друга не понимаем smile.gif.

Угу.
Код
typedef struct
{
   u8_t buf[BUF_SIZE]; // обычный кольцевой буфер
   u8_t *head, *tail, cnt; // можно обойтись без cnt
// имхо, BRGH можно на Ваших скоростях не трогать
   u8_t baud;
} usart_buf_t;

volatile usart_buf_t buf1, buf2;

void SendByteUart(u8_t data, usart_buf_t *pbuf)
{
   while(pbuf->cnt == BUF_SIZE) continue;
   *pbuf->head = data;
   if(++pbuf->head == &pbuf->buf + BUF_SIZE) pbuf->head = &pbuf->buf;
   pbuf->cnt++;
   TXIE = 1; // естесно, отсюда мы сразу улетим в прерывание
// поэтому, возможно, следующий вариант будет интересней
// if(++pbuf->cnt == FLUSH_USART_CNT) { FlushUsartTmr = 0; TXIE = 1; }
// else if(!FlushUsartTmr) FlushUsartTmr = FLUSH_USART_TIME;
// и где-нибудь, где обрабатываюся программные таймеры, добавить
// if(FlushUsartTmr) if(!--FlushUsartTmr) TXIE = 1;
}
// имхо (после некоторых раздумий), для мелких пиков таки разумнее будет
// сделать персональные SendByteUart() для каждого буфера
// &pbuf->buf - для совместимости разных версий хт писс между собой :)

void main(void)
{
...
   buf1.baud = BAUD1;
   buf2.baud = BAUD2;
   SendByteUart('a', &buf1);
   SendByteUart('b', &buf2);
...
}

interrupt void isr(void)
{
...
   if(TXIE && TXIF)
   {
      if(buf1.cnt)
      {
         if((SPBRG != buf1.baud) && !TRMT) break;
         SPBRG = buf1.baud;
         TXREG = *buf1.tail;
         buf1.cnt--;
         if(++buf1.tail == &buf1.buf + BUF_SIZE) buf1.tail = &buf1.buf;
      }
      else if(buf2.cnt)
      {
         if((SPBRG != buf2.baud) && !TRMT) break;
         SPBRG = buf2.baud;
         TXREG = *buf2.tail;
         buf2.cnt--;
         if(++buf2.tail == &buf2.buf + BUF_SIZE) buf2.tail = &buf2.buf;
      }
      else TXIE = 0;
   }
...
}

Цитата
Но ф-цию SendByteUART мне приходилось (сейчас концепция слегка переработана) вызывать, как в ф-ции main, так и в обработчике прерываний

Иногда _отладочную_ инфу мне приходится выдавливать в УСАРТ непосредственно в прерывании, но через отдельный отладочный буфер.
Цитата
(и никуда деться нельзя).

Станиславский - рулезз форева. smile.gif
Mad_max
Если еще, не решили свою проблему, то вот почитайте
http://www.microchip.su/showthread.php?t=2...%E2%E0%ED%E8%E9
не так давно тоже с этим маился.

P.S. а функции и правда нехорошо использовать в ISR и объем кода получается больше и нестабильности добавляет все это дело. Используйте флаги, а в майне их анализируйте.
Grigorij
Цитата(Mad_max @ Jun 7 2008, 17:10) *
Если еще, не решили свою проблему, то вот почитайте
http://www.microchip.su/showthread.php?t=2...%E2%E0%ED%E8%E9
не так давно тоже с этим маился.

P.S. а функции и правда нехорошо использовать в ISR и объем кода получается больше и нестабильности добавляет все это дело. Используйте флаги, а в майне их анализируйте.


Проблема уже давно решилась smile.gif. При этом на настоящее время есть 3 варианта решения:

1) #pragma interrupt level
2) использование флагов (собственно я остановился на этом варианте)
3) как недавно оказалась, в версии HI-TECH 9.60 PRO такой проблемы не возникает (специально пробовал одну и туже ф-цию вызывать как в main-е так и ISR, компилятор ругаться не стал)

Думаю на этом тему можно закрыть.
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.