Jump to content

    

Пару вопросов по Си для MCS51

Люди!!! Помогите. Пока перебросил переменные одной процедуры в XDATA, чтобы они ушли из перекрестка.

 

Вот настройки проекта:

post-2276-1228906497_thumb.jpg

post-2276-1228906532_thumb.jpg

post-2276-1228906558_thumb.jpg

post-2276-1228906574_thumb.jpg

post-2276-1228906582_thumb.jpg

post-2276-1228906588_thumb.jpg

Share this post


Link to post
Share on other sites
Суть проблеммы

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

Нашел в карте памяти что данные ячейки относятся еще к двум процедурам (причем там эти переменные тоже локальные). Одна из которых вызывается в прерывании.

Как мне теперь заставить Keil убрать из перекрестных ссылок эти переменные (чтобы они лежали в разных адресах DATA)?

Делать их глобальными внутри одного файла желания нет.

 

Пока я проверил только локальные переменные в области DATA, быть может такая петрушка есть еще с переменными в области XDATA. Почему Keil их так расположил? Ведь при компиляции он знает какие процедуры попадают в прерывание и по идее должен этим процедурам для локальных переменных выдилять свои адреса. Может нужно что-то указать??

 

Спасибо.

Как я понял, у Вас есть функция которая вызывается из функции обработки прерывания? Если так, то это очень не гуд :). Для этого нужны очень веские основания. Только в том случае, если Вы эту функцию вызываете еще из другого места. Но эту ситуацию линкер возможно и не отслеживает и нужно в этой функции переменные объявить static, хотя это равносильно глобальной переменной. Да и тем более возможен повторный вход в функцию со всеми вытекающими. Этот вариант отметаем :(

Если же вызов только из прерывания, тогда лучше вобще обойтись без дополнительной функции, все стащить в одно место, в функцию-обработчик. Вы же наверняка пользуетесь using-ами, может быть линкер эти переменные через регистры протащит, будет маленькое счастье :). А нельзя вобще обойтись в прерывании без обработки, выставив соответствующий флажок и обработать в другом месте?

Share this post


Link to post
Share on other sites
Как я понял, у Вас есть функция которая вызывается из функции обработки прерывания? Если так, то это очень не гуд :). Для этого нужны очень веские основания. Только в том случае, если Вы эту функцию вызываете еще из другого места. Но эту ситуацию линкер возможно и не отслеживает и нужно в этой функции переменные объявить static, хотя это равносильно глобальной переменной. Да и тем более возможен повторный вход в функцию со всеми вытекающими. Этот вариант отметаем :(

Если же вызов только из прерывания, тогда лучше вобще обойтись без дополнительной функции, все стащить в одно место, в функцию-обработчик. Вы же наверняка пользуетесь using-ами, может быть линкер эти переменные через регистры протащит, будет маленькое счастье :). А нельзя вобще обойтись в прерывании без обработки, выставив соответствующий флажок и обработать в другом месте?

 

Огромное спасибо за содержательный ответ.

 

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

 

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

 

Вы подняли мне веки :)

 

Правда это не отменяет объединения (отображения части переменных по одним и тем же адресам) локальных переменных двух разных процедур (я пока просто перебросил переменные в одной процедуре в XRAM и таким образом развязал эти процедуры).

 

Буду переписывать обработчик.

Edited by AndreyS

Share this post


Link to post
Share on other sites

Если функция не написана, как реентерабельная (слово не я придумал!) и при этом может вызываться из основной петли и из прерываний, то это, вобщем, конкретные грабли.

Share this post


Link to post
Share on other sites
Правда это не отменяет объединения (отображения части переменных по одним и тем же адресам) локальных переменных двух разных процедур (я пока просто перебросил переменные в одной процедуре в XRAM и таким образом развязал эти процедуры).

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

Share this post


Link to post
Share on other sites
В этом и соль этих переменных в отличии от глобальных.

 

Уверяю вас соль не в этом. Как известно памяти у 51 немного, плюс компилятор разработан так что локальные переменные сохраняет не на стеке, а либо в регистрах, либо в памяти (в этом вы можете убедится просмотрев руководство). В случае когда переменные располагаются в памяти линковщик может перекрывать область памяти локальных переменных одной подпрограммы и такую же область другой подпрограммы. Видимо так сделано с целью экономии памяти. Тогда, конечно подразумевается что эти фукции к примеру, не могут вызывать друг друга из своего тела. Так линковщик работает если ему не указать опцию nooverlay. Когда данная опция указана области локальных переменных подпрограмм не пересекаются и следовательно проблема порчи локальных переменных исчезает.

nooverlay.bmp

Share this post


Link to post
Share on other sites
Уверяю вас соль не в этом. Как известно памяти у 51 немного, плюс компилятор разработан так что локальные переменные сохраняет не на стеке, а либо в регистрах, либо в памяти (в этом вы можете убедится просмотрев руководство). В случае когда переменные располагаются в памяти линковщик может перекрывать область памяти локальных переменных одной подпрограммы и такую же область другой подпрограммы...

А я разве сказал не тоже самое?:-D

Share this post


Link to post
Share on other sites
Если две функции не могут вызываться одновременно (случай с прерыванием) - то конечно же пусть они используют одни и те же адреса для локальных переменных. В этом и соль этих переменных в отличии от глобальных. Если посмотреть на листинг то почти в каждой функции для локальных переменных используются регистры. И это очень хорошо, это только плюс компилятору-линкеру. Ведь памяти очень немного :)

 

Добрый день.

 

Спасибо всем за ответы.

Все что Вы ранее высказывали, я прекрасно понимаю.

Но тут вещь в следующем.

Компилятор никогда не отображает локальные переменные на одни и теже адреса, если они пересекаются при вызове процедур (РАЗНЫХ ПРОЦЕДУР). Если переменные пересекаются (отображаются на одни и теже адреса) (РЕГИСТРЫ НАПРИМЕР), то компилятор при входе в такую процедуру сохраняет все эти регистры в стек. Если компилятор этого не делает - то это плохой компилятор. Анализирую код Keil'а он (Кейл) именно так и делает. Если я в перерывании (или процедуре) использую вызов процедуры, то в прерывании, перед ее выполнением, сохряняются все регистры и PSW в стек. Если я неиспользую каких либо регистров (ну компилятор в процессе компиляции ничего не задействовал), то на входе он ничего не сохраняет. Для Кейла я это проверял. Правда весь листинг виден только в самом проце, в выходных файлах этого нет.

 

В данном примере происходит все именно так.

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

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

 

Есть функция Spi_RD в которую я передаю ряд параметров. Она вызывается в прерывании.

Есть функция Filtrate_long в которую я передаю одно число и в нутри нее есть переменные счетчики.

Так пересеклись две переменные из входящих переменных функции Spi_RD и внутренние переменные-счетчики процедуры Filtrate_long.

 

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

 

Вот вызовы процедур с которыми я нашел пересечение переменных:

bit Spi_RD(byte * ptr, byte * complete, word LEN, byte type, byte FIRST_SENT, bit cs);

signed long filtrate_long(signed long input_data);

 

Пересеклись адреса следующих переменных

type, FIRST_SENT с локальными переменными (счетчиками) внутри функции Filtrate_long.

 

В данный момент локальные переменные функции Filtrate_long я объявил в области XDATA дабы они попросту ушли из карты памяти в другую область (остальные вроде как ен пересекаются). И все работает без глюков.

 

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

 

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

 

 

Как известно памяти у 51 немного, плюс компилятор разработан так что локальные переменные сохраняет не на стеке, а либо в регистрах, либо в памяти (в этом вы можете убедится просмотрев руководство). В случае когда переменные располагаются в памяти линковщик может перекрывать область памяти локальных переменных одной подпрограммы и такую же область другой подпрограммы. Видимо так сделано с целью экономии памяти. Тогда, конечно подразумевается что эти фукции к примеру, не могут вызывать друг друга из своего тела. Так линковщик работает если ему не указать опцию nooverlay. Когда данная опция указана области локальных переменных подпрограмм не пересекаются и следовательно проблема порчи локальных переменных исчезает.

 

Добрый день.

 

Огромное вам спасибо.

 

Мне кажется - это есть лекарство моей болезни. Так как симптомы вы описали точно.

 

Черт, а ведь эта директива относится ко всему проекту. С ней области data не хватает :)

А для лечения локально, нужно делать глобальбные переменные. А что же делать с входыми данными процедур??? Тоже глобально??

Edited by AndreyS

Share this post


Link to post
Share on other sites
Компилятор никогда не отображает локальные переменные на одни и теже адреса, если они пересекаются при вызове процедур (РАЗНЫХ ПРОЦЕДУР). Если переменные пересекаются (отображаются на одни и теже адреса) (РЕГИСТРЫ НАПРИМЕР), то компилятор при входе в такую процедуру сохраняет все эти регистры в стек. Если компилятор этого не делает - то это плохой компилятор. Анализирую код Keil'а он (Кейл) именно так и делает.

Здесь позвольте с Вами не согласиться. Это относится не конкретно к Keil, а вообще к языку C. Локальные переменные, кроме того что они видны только внутри функции (блока), они еще имеют время жизни только на протяжении выполнения этой функции (блока). Ни в коем случае не гарантируется, что при очередном вызове функции локальная переменная будет иметь тоже значение, что было раньше.

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

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

В данном примере я заметил пересекающиеся области...

Чтобы не сохранять регистры лучше использовать using. А вот для этого случая - просто функции и функции вызываемой из прерывания, Keil действительно подкачал :), видимо у него нет такой проверки на перекрытие переменных :(. Я когда-то тоже обжигался на этом. Теперь прерывания только наиболее короткие, без вызовов, с использованием отдельного банка памяти для прерываний одинакового приоритета.

Правда весь листинг виден только в самом проце, в выходных файлах этого нет.

А в *.LST разве в конце не расписаны адреса под переменные (при соответствующих галочках в настройках), или это не то?

 

bit Spi_RD(byte * ptr, byte * complete, word LEN, byte type, byte FIRST_SENT, bit cs);

Меня, честно говоря, пугают такие функции с огромным количеством входных переменных :). Не воспринимайте мои слова близко к сердцу. Это конечно не ошибка, просто я бы использовал структуру и передавал бы указатель на нее.

Edited by barabek

Share this post


Link to post
Share on other sites
Здесь позвольте с Вами не согласиться. Это относится не конкретно к Keil, а вообще к языку C. Локальные переменные, кроме того что они видны только внутри функции (блока), они еще имеют время жизни только на протяжении выполнения этой функции (блока). Ни в коем случае не гарантируется, что при очередном вызове функции локальная переменная будет иметь тоже значение, что было раньше.

 

Меня, честно говоря, пугают такие функции с огромным количеством входных переменных :). Не воспринимайте мои слова близко к сердцу. Это конечно не ошибка, просто я бы использовал структуру и передавал бы указатель на нее.

 

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

 

Обидно все это. Регистры сохраняет (так как знает что их портит), а часть используемых ячеек не сохраняет. :(

Share this post


Link to post
Share on other sites

Добрый день.

 

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

 

Пересекающихся адресов теперь нет. Все работает нормально.

 

Печально это :(

И печально именно то, что оказалось что Keil не сохраняет все локальные переменные при входе в процедуры. Сохраняет он только регистры. :(

 

А еще более печально то, что прошло столько времени, а подобное в нем досих пор существует.

 

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

 

Мда. Что еще может подстерегать???

 

По сему заинтересовал вопрос перехода с Keil на другой компилятор и линковщик. Или просто другой линковщик (вроде как именно он раскидывает переменые по адресам???).

 

Как себя ведут другие компиляторы касательно данной проблеммы??

 

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

Share this post


Link to post
Share on other sites
И печально именно то, что оказалось что Keil не сохраняет все локальные переменные при входе в процедуры. Сохраняет он только регистры. :(
Смею предположить, что Кейл все делает правильно. Для цепочки вызовов он строит компилируемый стек и вы сами заметили, что локальные переменные разных функций в такой цепочке не пересекаются. Axel в сообщении №19 сказал вам, что ваш случай отличается от обычного, что вам надо найти способ указать компилятору, что функция является реентерабельной. Штудируйте документацию компилятора на предмет ключевого слова reentrant или подобного. Если бы компилятор каждую функцию делал реентерабельной - вы бы первый взвыли, что он сохраняет кучу лишнего и генерит неэффективный код. Кейлом и x51 уже очень давно не пользуюсь, поэтому не могу подсказать конкретнее, но на 99% уверен, что в нем предусмотрен какой-то атрибут вроде reentrant для функций.

Share this post


Link to post
Share on other sites
Смею предположить, что Кейл все делает правильно.

...вам надо найти способ указать компилятору, что функция является реентерабельной.

Штудируйте документацию компилятора на предмет ключевого слова reentrant или подобного.

c51.pdf

page 129

Reentrant functions can be called recursively and can be called simultaneously

by two or more processes. Reentrant functions are often required in real-time

applications or in situations where interrupt code and non-interrupt code must

share a function.

 

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

совместно использовать функцию.

Edited by c8051

Share this post


Link to post
Share on other sites
Фух... Кейла оправдали. Очередной "глюк" оказался просто ленью прочитать документацию.

 

Добрый день.

 

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

 

Большое спасибо за наставление.

Пойду читать документацию (просто читать ее всю подряд, от корки до корки, я не хотел. В этом я действительно поленился :).

 

Спасибо.

 

PS. Да а на сообщение Axel я не обратил внимания :laughing:

Edited by AndreyS

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this