Если взять 10 шт. if() и 10 шт. case и измерять время выполнения рутины при сработке третьего по счету (в исходнике) условия, то оно может быть больше в случае с case, т.к. компилятор равновероятно соптимизирует код, запихав это 3-е условие в конец таблицы переходов.
У элементов логики фронты импульсов бывают больше 10 микросекунд. Не говоря уже об механики аппарата, который будет осуществлять работу. Или это просто какая то идеальная система, разработанная 20 лет назад. Такое впечатление, что автор теоретик, пишущий очередную диссертацию, ради того, чтобы было. 20 монет в секунду, да ещё и с сортировкой фальшивых, это фантастика. Может быть нам автор всё же раскроет секрет, где работает двадцать лет данная система? И для чего её всё же решили усовершенствовать? А так всё это жесткий тролинг форума IMHO.
но я бы не советовал погружаться туда глубоко. Компилятор сделает код ТОЧНО быстрее, чем неопытный человек. Чтобы "обнулить" переменную нужно записать по её адресу 0, если переменная однобайтовая или несколько нолей, по размеру переменной. С учетом того, что компилятор один регистр оставляет для константы 0, каждая запись в ОЗУ - 2 такта. При компиляции программы, если переменная не имеет глобального применения, компилятор часто размещает её в регистре. Тогда все меняется.
Вывод из п.1. - не стоит городить ассемблерные вставки, тем более если ты не знаешь РИСК-ассемблера и особенности компилятора. Ты потратишь очень много времени на обучение.
2. Теперь к сути задачи: При тестировании получаем примерно 10 (пусть 20) импульсов по не более 10 мкс, так? На все меньше 500 мкс, так? Сама идея ожидать паузу между импульсами, вызывая прерывания каждые 10 мкс - порочна. в самом минимуме такое прерывание (аккуратно написанное) займет от 5 мкс, то есть ПОЛОВИНУ процессорного времени, на остальную работу ничего не останется.
В этом случае делают так: 1 вариант - остановить (Обязательно!) другие прерывания, так как в АВР нет приоритета прерываний, на время тестирования и в этот момент не делать, кроме ожидания прекращения импульсов, ВООБЩЕ НИЧЕГО.
2 вариант, более правильный: смотрим по таблице ожидаемых результатов и видим, что (к примеру) 20 импульсов уже точно не соответствуют никакой подлинной монете. Значит мы можем посчитать импульсы на 200 мкс и сравнить полученное количество с таблицей, так? Импульсы нужно считать таймером, если мы не хотим запрещать прерывания. 200 мкс можно просто отмерить встроенной функцией micros(). 200-500 мкс не заметит никакая другая часть твоей программы. Ни дисплей, ни вывод в сериал, при правильном программировании. Монета в тракте за пол миллисекунды сместится максимум на 1 мм.
Этот прием в программировании используется часто. Мы заранее жертвуем часть ресурса, в данном случае фиксируем максимальное время проверки и пренебрегаем возможностью закончить тестирование раньше, поскольку трата ресурсов на отслеживание точного завершения проверки в десятки раз больше, чем просто зафиксировать время теста в, скажем, 300 мкс.
----------------------
Понял ли и сможешь ли написать такое сам?
------------------------
ЗЫ: я выше по тексту увидел, как ты заявил, что switch быстрее серии else if. Для АВР это совершенно наоборот. Видно, что ты неплохо знал особенности работы на обычном ПК. Там другая архитектура и там выбор switch case был верен.
Спасибо за ссылку, нужно очень вдумчиво читать. Статья хорошая, разжёванная по полочкам!
Вопрос такой, (так как пока не смотрел ассемблерный код, сгенерированный компилятором, который пока не знаю как посмотреть):
Допустим есть команда
Coin_OK = false;
Если я такие и подобные команды буду заменять ассемблерными вставками такими как
asm volatile ("clr %0" : "=r" (Coin_OK));
не означает что я буду казаться хотеть умнее компилятора, то что он и так такие ассемблерные команды вставляет вместо Си-шных команд? Мне кажется, что так оно и есть. Но хотя мысли такие, что, допустим, смотрим в цикле переменную и, если она равна 0, то максимально быстро (меняем уровень пина порта работы двигателя) останавливаем двигатель такой вот командой
но не знаю всю "поднаготную", как прошивает компилятор, какими командами после компиляции мою Ардуину.
"При компиляции программы, если переменная не имеет глобального применения, компилятор часто размещает её в регистре. Тогда все меняется."
Про это я бы потом с Вами поговорил, мне это пока непонятно.
2) Далее, я тоже прихожу к выводу, что 10 мкс это очень мало. Про 10 мкс мне сказали здесь, что программа машины проверяет именно через такое время. Я и уперся в это число, но по Вашим словам прихожу к выводу что можно увеличить это время. Нужно ещё раз перелопатить логику программы. Программа большая по моим меркам, для меня это первый серьёзный проект.
Ваши 2 варианта алгоритма работы я продумаю, попробую что-то поменять. Плохо то, что у меня пока есть только код, датчики ещё даже не подключал, схема не разработана. Сделал пока меню на дисплее, чтобы всё правильно работало, и пока имитация подсчёта монет через случайные данные функцией random().
Думаю, что понял Ваши мысли, спасибо за направления, буду пробовать.
И ещё, я мало знаю в ПК, программировал в Дельфи давно. А разницу работы switch | if-else, взял из сайта Гайвера
У элементов логики фронты импульсов бывают больше 10 микросекунд. Не говоря уже об механики аппарата, который будет осуществлять работу. Или это просто какая то идеальная система, разработанная 20 лет назад. Такое впечатление, что автор теоретик, пишущий очередную диссертацию, ради того, чтобы было. 20 монет в секунду, да ещё и с сортировкой фальшивых, это фантастика. Может быть нам автор всё же раскроет секрет, где работает двадцать лет данная система? И для чего её всё же решили усовершенствовать? А так всё это жесткий тролинг форума IMHO.
Парни, я же писал просьбу не писать подобные заявления. ЗАО НПП "АДОНИС". Продукция банковская техника - АСМ
/путь к ИДЕ/hardware/tools/avr/bin/avr-objdump -S твой скетч.elf > xxxxx.S
где твой скечт.elf - елф-файл в tmp директории, где ИДЕ собирает проекты
xxxxx.S придумай имя файла для вывода и смотри свой ассемблерный код
AlexBajdin59rus пишет:
asm volatile ("clr %0" : "=r" (Coin_OK));
clr очистка РЕГИСТРА! Не любой переменной. Риск архитектура, да ещ Гарвардская сильно отличается от привычного тебе асма ПК.
AlexBajdin59rus пишет:
Я к тому, что вроде и пишу вместо Ардуиновских
digitalWrite()
вот так
PORT_WORK &= ~(MOTOR_REVERSE | MOTOR_COUNTER)
Вот так и пиши. Это и правильно и оптимально. Если нет ситуации, критичной по времени - пользуйся digitalWrite(). Есть 100500 способов сделать ошибку в коде, не плоди новых. В твоей задаче НИГДЕ не нужно гнаться за тактами, честно. :)) Ты просто немножко неправильно подошел.
AlexBajdin59rus пишет:
"При компиляции программы, если переменная не имеет глобального применения, компилятор часто размещает её в регистре. Тогда все меняется."
Про это я бы потом с Вами поговорил, мне это пока непонятно.
это значит, что для многих переменных компилятор вообще не отводит памяти. Если не указать этого специально.
AlexBajdin59rus пишет:
Думаю, что понял Ваши мысли, спасибо за направления, буду пробовать.
И ещё, я мало знаю в ПК, программировал в Дельфи давно. А разницу работы switch | if-else, взял из сайта Гайвера
Гайвер - воплощенное ЗЛО! ;))))) Пример популяризация "себя любимого" без какого либо понимания предмета.
основная проблема тут не в этом такте, а в сохранении и восстановлении регистров при входе выходе в прерывание. Местные аборигены не компетентно.
а можешь обьяснить - нафига конкретно в этом коде сохранять и восстанавливать регистры? Желательно честко и ясно, а не в стиле архата, который чуть что не так - включает обиженку
Потому как для исполнения кода обработчика прерываний процу нужно где-то хранить данные. Это "где-то" в первую очередь регистры. Но на момент прерывания эти регистры могут использоваться в соответствии с кодом основной программы. Чтоб их не затереть своими данными при вхождении в обработчик их запихивают в стек командой PUSH, а это 2 такта, а по завершению возвращают из стека обратно РОР, тоже 2 такта. Потому в идеале надо чтоб обработчик использовал минимум регистров и не сохранял лишних. Компилятор обычно этими 2-я вопросами не сильно озабочен. Потому и имеем вход+выход в пустое прерывание под 80 тактов. Но Ахат , помница, говорил что ему там че-то удалось сокращать. Если не врал конечно.
А конкретно этот код не выглядит нуждающимся в большом числе регистров для обработчика, обработчик в общем легкий. Потому наверняка удастся освободить хоть пару-тройку регистров, а это десяток тактов свободных. На фоне того одного такта, что ТС искал в начале темы - целое море ресурса )))
Проще.) Компилятор сохраняет в стеке, используемые в функции прерывания, регистры. Если в функции прерывания используются и другие (дополнительные) функции (компилятор не может определить используемые регистры), тогда контекст сохраняется по максимуму, т.е. все регистры!
тогда контекст сохраняется по максимуму, т.е. все регистры!
Ну такое я тоже видел, но в более новых версиях лучше, он сохраняет только нужные. Если б еще и оптимизировал код для сокращения используемых регистров! А че воще значить "компилятор не может определить используемые регистры" - ему все исходники дали, как так "не может"? Он плохо старается... Может речь о линковке с библиотечными функциями?
А че воще значить "компилятор не может определить используемые регистры" - ему все исходники дали, как так "не может"? Он плохо старается...
Ленится.) А может, в этой функции, там ещё тыща функций вызывается?) Нет, компилятор не может видеть содержимое вызываемой функции, он видит только прототип, а функция может быть вообще в другом модуле.
Продолжу по своей теме. Согласен с тем, что нужно и можно увеличить период вызова прерывания Таймера до 20-30 мкс. Это конечно всё равно мало. Нужно оптимизировать уже написанный код основной программы. В ней тоже происходит какая-либо работа - опрашиваются микровыключатели на размыкание цепи (при размыкания происходит немедленный останов), опрашивается сенсор дисплея. Сенсор понятно, можно опрашивать 10-20 раз в секунду, биты в регистре выставлять вручную и через прерывание, а не функцией analogRead() выявлять факт касания сенсора, но когда идет его опрос, то всё равно этот опрос пересекается с работой датчиков. Микрики через простые опросы пинов опрашиваются быстро. Но мне всё равно хотелось бы, так как и интересно и нужно (придёт и к этому моменту) попробовать написать ассемблерные вставки. Запись в порт МК через ассемблер я понял как
А че воще значить "компилятор не может определить используемые регистры" - ему все исходники дали, как так "не может"? Он плохо старается...
Ленится.) А может, в этой функции, там ещё тыща функций вызывается?) Нет, компилятор не может видеть содержимое вызываемой функции, он видит только прототип, а функция может быть вообще в другом модуле.
Исходники ее есть в проекте, я вижу, мой кот видит, а компилятор не может... Если оставить в стороне случай, когда функция заранее скомпилирована в библиотеку, линкуется к проекту и ее исходники отсутствуют, то таки да, он плохо старается...
Ну так уж зарубатся особого смысла нет, кроме особых случаев*. PORTB|=0x01 скорей всего и станет "sbi %0, 0x01".
Я в случаях средней сложности просто дизассамблирую что получилось и смотрю на сколько все плохо. Для этого в батнике держу что-то типа "c:\Program Files (x86)\Arduino\hardware\tools\avr\bin\avr-objdump.exe" -d sketch_sep18a.ino.elf >dtmf.asm . Файл *.ino.elf из папки build
Для побитовой работы с портами пользуюсь bitSet, bitClear, bit_is_set, bit_is_clear Обычно все норм при использовании констант.
*Про особый случай. WS2812b. Написал на ассемблерной вставке - работает. Переписал на си, контролируя чтоб получилось похожим как на асм - работает. Поменялись настройки - не работает, поменялась версия ИДЕ - не работает, поменялась фаза луны - не работает. Вытер нах, оставил на ассемблере.
Помогаю: это не нужно делать ассемблерными вставками. Они НЕ ускорят работу программы.
Использовать в таких случаях ассемблер - просто мастурбация, т.е. занятие безусловно приятное, но совершенно бесплодное. Ассемблер применяют в критичных по времени исполнения кусках и те программисты, которые реально могут написать код, более быстрый, чем компилятор. Пример: библиотека fastled, вероятно единственный в Ардуино пример оправданного использования ассемблера.
Если ты просто хочешь изучить использование вставок, то это дело благое, но к твоей задаче отношения не имеет. Устанавливать переменную или писать в порт ассемблером - вообще чушь! Эти операции компилятор всегда сделает лучше сам.
Я же научил тебя получать листинг на асме? Так сам посмотри, что и как делает компилятор.
---------------
Еще раз и последний, если продолжишь ипать свое безумное прерывание - дальше участваовать не стану. Я написал тебе, что ожидать прекращения импульсов в прерывании - дикий бред. Настаиваешь на своем? Ок, но уже без меня.
Неправда, если бы Вы действительно просили помощи, Вы бы отвечали на вопросы, которые Вам задают.
Я Вас как человека спросил
ЕвгенийП пишет:
AlexBajdin59rus пишет:
но может пока ищу, Вы мне быстрее подскажите как например выставить на порту В пин в высокий уровень...?
Пробую так:
asm volatile( "sbi PORTB, 3" );
не получается...
Какой пин Вы здесь выставляете?
Где ответ?
Также я дал Вам ссылку на руководство по машинным командам. Судя по дальнейшему, оно не прочитано. Так какая помощь Вам нужна, если Вы сами не работаете?
Что значит "не проходит"? МК взрывается, гаснет свет во всём доме, или что? Что именно идёт не так?
Во-первых, приводите полный скетч, который я мог бы сам запустить и посмотреть.
Во-вторых, потрудитесь внятно объяснять что у Вас не работает. У меня, например, такая инструкция "проходит" на ура! Значит, у Вас что-то другое "не проходит" в той части скетча, которую Вы не показали.
AlexBajdin59rus пишет:
Вроде много где почитал, но конкретных примеров нет.
Не знаю, что и где Вы читаете (Вы, правда, задолбали своими совершенно неконкретными "не проходит", "много где" и т.п. Выражайтесь конкретнее и яснее).
Правильно читать фирменную документацию. Но, если Вы не умеете читать (на языке оригинала), то попробуйте, например, вот этот пост - вполне адекватное изложение с херовой тучей примеров.
Неправда, если бы Вы действительно просили помощи, Вы бы отвечали на вопросы, которые Вам задают.
Я Вас как человека спросил
ЕвгенийП пишет:
AlexBajdin59rus пишет:
но может пока ищу, Вы мне быстрее подскажите как например выставить на порту В пин в высокий уровень...?
Пробую так:
asm volatile( "sbi PORTB, 3" );
не получается...
Какой пин Вы здесь выставляете?
Где ответ?
Также я дал Вам ссылку на руководство по машинным командам. Судя по дальнейшему, оно не прочитано. Так какая помощь Вам нужна, если Вы сами не работаете?
Что значит "не проходит"? МК взрывается, гаснет свет во всём доме, или что? Что именно идёт не так?
Во-первых, приводите полный скетч, который я мог бы сам запустить и посмотреть.
Во-вторых, потрудитесь внятно объяснять что у Вас не работает. У меня, например, такая инструкция "проходит" на ура! Значит, у Вас что-то другое "не проходит" в той части скетча, которую Вы не показали.
AlexBajdin59rus пишет:
Вроде много где почитал, но конкретных примеров нет.
Не знаю, что и где Вы читаете (Вы, правда, задолбали своими совершенно неконкретными "не проходит", "много где" и т.п. Выражайтесь конкретнее и яснее).
Правильно читать фирменную документацию. Но, если Вы не умеете читать (на языке оригинала), то попробуйте, например, вот этот пост - вполне адекватное изложение с херовой тучей примеров.
Не знаю как вы много раз цитируете...
1) Да, действительно я тогда был не прав, на тот момент не знал синтаксиса этой инструкции. Я тогда хотел выставить 3 бит, а выставлял нулевой и первый.
2) Видимо немного запутался и не то написал. Инструкция проходит, да, а вот дальнейшие действия неправильные.
var = 0xF0;
PORTB = 0x00;
asm volatile ("in %0, %1" : "=r" (var) : "I" (_SFR_IO_ADDR(PORTB)));
Serial.println(String(var) + " - var asm"); // выводит 0x00
var = 0xFF;
// а вот тут я что-то неправильно делаю
asm volatile ("out %1, %0" : "=r" (var) : "I" (_SFR_IO_ADDR(PORTB)));
Serial.println(String(PORTB) + " - PORTB"); // НЕПРАВИЛЬНО! выводит 0x1B
// что я делаю не так ??? модификатор нужен другой ???
3) А что, нужно конкретные ссылки приводить...? Набираешь "ассемблерные вставки" в поисковике и они одни и те же у всех вылазят.
4) перечитываю ещё раз ТОТ ПОСТ вроде начинает доходить, стр. 8 этой книги вроде как пример для Си-шных переменных...?
Помогаю: это не нужно делать ассемблерными вставками. Они НЕ ускорят работу программы.
Использовать в таких случаях ассемблер - просто мастурбация, т.е. занятие безусловно приятное, но совершенно бесплодное. Ассемблер применяют в критичных по времени исполнения кусках и те программисты, которые реально могут написать код, более быстрый, чем компилятор. Пример: библиотека fastled, вероятно единственный в Ардуино пример оправданного использования ассемблера.
Если ты просто хочешь изучить использование вставок, то это дело благое, но к твоей задаче отношения не имеет. Устанавливать переменную или писать в порт ассемблером - вообще чушь! Эти операции компилятор всегда сделает лучше сам.
Я же научил тебя получать листинг на асме? Так сам посмотри, что и как делает компилятор.
---------------
Еще раз и последний, если продолжишь ипать свое безумное прерывание - дальше участваовать не стану. Я написал тебе, что ожидать прекращения импульсов в прерывании - дикий бред. Настаиваешь на своем? Ок, но уже без меня.
1) Занятие конечно приятное, помню, давно пробовал писать программу на Асме для PIC-контроллера.
2) Попробовать -то надо... :-)
3) Fasted нашел, буду смотреть (она и ТОЙ ССЫЛКЕ есть)
4) Листинга на Асме нет в ПлатформИО, там только бинарный код... :-( Нужно попробовать через АтмелСтудио посмотреть...
5) Всё таки буду пробовать ожидать окончания импульсов в прерывании - какие-то минимальные сравнения делать там и оттуда получать выводы (или булевую переменную о выявлении сурогата)
Какие-то "обидки" у Вас... Я написал код, прокомментировал что выводит при этом коде, если человек знающий, сразу поймёт в чём дело. Зачем запускать код у себя, если вот он? Я понимаю что тут то-ли адрес, то-ли смещение, то-ли ещё чего (совсем другой регистр из 32-х ассемблерных) передаётся в порт, но как это исправить пока не в моих силах, знаний и опыт ане хватает. Так нет, чтобы подсказать, пишете ерунду...
Давайте писать по существу и не более, зачем хламить мою ветку форума
Какие? Вы, видимо, можете разбираться в программах не запуская их, а я - нет. Не умею я так. Поэтому я предложил Вам работать самому и пожелал удачи. Никаких обидок!
AlexBajdin59rus пишет:
Я написал код, прокомментировал что выводит при этом коде, если человек знающий, сразу поймёт в чём дело. Зачем запускать код у себя, если вот он?
Вы точно уверены, что Вы лучше меня знаете, что и как мне делать, чтобы дать Вам дельный совет? Ну, если Вы знаете лучше, то и разбирайтесь сами, я Вам уже сказал.
Я, видимо, не "человек знающий", а потому, мне для того, чтобы Вам что-то точно подсказать, нужно:
запустить Ваш код;
увидеть проблему самому;
поправить ошибку;
запустить ешё раз;
убедиться, что проблема ушла;
и только тогда Вам подсказывать.
Я всегда так делаю. Если Вас такой подход не устраивает - продолжайте без меня.
AlexBajdin59rus пишет:
Давайте писать по существу и не более, зачем хламить мою ветку форума
Так вроде я с Вами уже попрощался, пожелал Вам удачи и не собирался её более захламлять. Это Вы зачем-то захламляете.
При выводе сначала читается в регистр содержимое ячейки памяти, где лежит var и потом содержимое этого регистра выводится в порт.
При чтении сначала читается значение из порта в регистр и потом содержимое регистра выводится в ячейку памяти, где лежит var.
ГДЕ ЭТО У ВАС ???
Как видно из листинга - НИЧЕГО ЛИШНЕГО для данного простого кода компилятор не придумал !
Вам тут что бесплатные курсы inline assembler ? Вы с обычным assembler сначала разберитесь ! А еще лучше для начала понять логику работы процессоров !!!
При выводе сначала читается в регистр содержимое ячейки памяти, где лежит var и потом содержимое этого регистра выводится в порт.
При чтении сначала читается значение из порта в регистр и потом содержимое регистра выводится в ячейку памяти, где лежит var.
ГДЕ ЭТО У ВАС ???
Как видно из листинга - НИЧЕГО ЛИШНЕГО для данного простого кода компилятор не придумал !
Вам тут что бесплатные курсы inline assembler ? Вы с обычным assembler сначала разберитесь ! А еще лучше для начала понять логику работы процессоров !!!
Да, но у меня же в переменную значение правильно записывается... А про запись значения в порт, я получается правильно предполагал, что не значение из переменной записывается, а что-то другое...
Да, но у меня же в переменную значение правильно записывается... А про запись значения в порт, я получается правильно предполагал, что не значение из переменной записывается, а что-то другое...
Уверены что правильно ??? Нельзя ничего записать в переменную без команды записи в память. Попробуйте значение отличное от дефолтного нуля и увидите. Команды IN OUT работают с регистром, а не с памятью !
Странные вольности он себе позволил ! Вместо одной команды - вставляет две !
А как "он" знает, что VAR находиться по адресу 0x800106? Это прописывается на этапе компиляции, сборки (не знаю точно правильное определение процесса)?
И я не понял:
а в другом нет:
asm volatile ("out %1, %0" : "=r" (var) : "I" (_SFR_IO_ADDR(PORTB)));
120: 85 b9 out 0x05, r24 ; 5
122: 80 93 06 01 sts 0x0106, r24 ; 0x800106 <var>
тут мы хотим записать значение из переменной в порт, что делается инструкцией:
120: 85 b9 out 0x05, r24 ; 5
Почему после неё присутствует инструкция:
122: 80 93 06 01 sts 0x0106, r24 ; 0x800106 <var>
Или это ваш код, который я не видел?
Я правильно Вас понял, что в этом странность, двух инструкций?
wdrakula А ну как просвети ... чего это во втором случае он не справился ?
нужно переменную в выходных указать. Компилятор грузит входные ДО кода, и выгружает регистры в ВЫходные после кода. Показать? Думаю уже сам понял. Во второй вставке переменная, как и в первой, указана входной, а первом блоке. А нужно через запятую во втором.
;))))
Там немного логика "черезжопная" в синтаксисе вставок, потому я ТС-у и не советую этим заниматься.
это работа с переменными, при вставках нужно же загрузить и выгрузить их. Там, чтобы выиграть такты, нужно ну вот-прям очень хорошо знать, что ты делаешь. ;)) Чаще всего это нужно для очень точного "ногодрыга" потому я и написал, что (ИМХО) единственное оправданное применение, которое я видел - библиоетка fastled.
Если взять 10 шт. if() и 10 шт. case и измерять время выполнения рутины при сработке третьего по счету (в исходнике) условия, то оно может быть больше в случае с case, т.к. компилятор равновероятно соптимизирует код, запихав это 3-е условие в конец таблицы переходов.
У элементов логики фронты импульсов бывают больше 10 микросекунд. Не говоря уже об механики аппарата, который будет осуществлять работу. Или это просто какая то идеальная система, разработанная 20 лет назад. Такое впечатление, что автор теоретик, пишущий очередную диссертацию, ради того, чтобы было. 20 монет в секунду, да ещё и с сортировкой фальшивых, это фантастика. Может быть нам автор всё же раскроет секрет, где работает двадцать лет данная система? И для чего её всё же решили усовершенствовать? А так всё это жесткий тролинг форума IMHO.
ОК.
Давай начнем.
1. Просто замечание. Я увидел, что ты интересуешься ассемблерными вставками. Вот тебе ссылка:
http://microsin.net/programming/avr/avr-gcc-inline-assembler.html
но я бы не советовал погружаться туда глубоко. Компилятор сделает код ТОЧНО быстрее, чем неопытный человек. Чтобы "обнулить" переменную нужно записать по её адресу 0, если переменная однобайтовая или несколько нолей, по размеру переменной. С учетом того, что компилятор один регистр оставляет для константы 0, каждая запись в ОЗУ - 2 такта. При компиляции программы, если переменная не имеет глобального применения, компилятор часто размещает её в регистре. Тогда все меняется.
Вывод из п.1. - не стоит городить ассемблерные вставки, тем более если ты не знаешь РИСК-ассемблера и особенности компилятора. Ты потратишь очень много времени на обучение.
2. Теперь к сути задачи: При тестировании получаем примерно 10 (пусть 20) импульсов по не более 10 мкс, так? На все меньше 500 мкс, так? Сама идея ожидать паузу между импульсами, вызывая прерывания каждые 10 мкс - порочна. в самом минимуме такое прерывание (аккуратно написанное) займет от 5 мкс, то есть ПОЛОВИНУ процессорного времени, на остальную работу ничего не останется.
В этом случае делают так: 1 вариант - остановить (Обязательно!) другие прерывания, так как в АВР нет приоритета прерываний, на время тестирования и в этот момент не делать, кроме ожидания прекращения импульсов, ВООБЩЕ НИЧЕГО.
2 вариант, более правильный: смотрим по таблице ожидаемых результатов и видим, что (к примеру) 20 импульсов уже точно не соответствуют никакой подлинной монете. Значит мы можем посчитать импульсы на 200 мкс и сравнить полученное количество с таблицей, так? Импульсы нужно считать таймером, если мы не хотим запрещать прерывания. 200 мкс можно просто отмерить встроенной функцией micros(). 200-500 мкс не заметит никакая другая часть твоей программы. Ни дисплей, ни вывод в сериал, при правильном программировании. Монета в тракте за пол миллисекунды сместится максимум на 1 мм.
Этот прием в программировании используется часто. Мы заранее жертвуем часть ресурса, в данном случае фиксируем максимальное время проверки и пренебрегаем возможностью закончить тестирование раньше, поскольку трата ресурсов на отслеживание точного завершения проверки в десятки раз больше, чем просто зафиксировать время теста в, скажем, 300 мкс.
----------------------
Понял ли и сможешь ли написать такое сам?
------------------------
ЗЫ: я выше по тексту увидел, как ты заявил, что switch быстрее серии else if. Для АВР это совершенно наоборот. Видно, что ты неплохо знал особенности работы на обычном ПК. Там другая архитектура и там выбор switch case был верен.
Спасибо за ссылку, нужно очень вдумчиво читать. Статья хорошая, разжёванная по полочкам!
Вопрос такой, (так как пока не смотрел ассемблерный код, сгенерированный компилятором, который пока не знаю как посмотреть):
Допустим есть команда
Если я такие и подобные команды буду заменять ассемблерными вставками такими как
не означает что я буду казаться хотеть умнее компилятора, то что он и так такие ассемблерные команды вставляет вместо Си-шных команд? Мне кажется, что так оно и есть. Но хотя мысли такие, что, допустим, смотрим в цикле переменную и, если она равна 0, то максимально быстро (меняем уровень пина порта работы двигателя) останавливаем двигатель такой вот командой
Я к тому, что вроде и пишу вместо Ардуиновских
вот так
но не знаю всю "поднаготную", как прошивает компилятор, какими командами после компиляции мою Ардуину.
"При компиляции программы, если переменная не имеет глобального применения, компилятор часто размещает её в регистре. Тогда все меняется."
Про это я бы потом с Вами поговорил, мне это пока непонятно.
2) Далее, я тоже прихожу к выводу, что 10 мкс это очень мало. Про 10 мкс мне сказали здесь, что программа машины проверяет именно через такое время. Я и уперся в это число, но по Вашим словам прихожу к выводу что можно увеличить это время. Нужно ещё раз перелопатить логику программы. Программа большая по моим меркам, для меня это первый серьёзный проект.
Ваши 2 варианта алгоритма работы я продумаю, попробую что-то поменять. Плохо то, что у меня пока есть только код, датчики ещё даже не подключал, схема не разработана. Сделал пока меню на дисплее, чтобы всё правильно работало, и пока имитация подсчёта монет через случайные данные функцией random().
Думаю, что понял Ваши мысли, спасибо за направления, буду пробовать.
И ещё, я мало знаю в ПК, программировал в Дельфи давно. А разницу работы switch | if-else, взял из сайта Гайвера
У элементов логики фронты импульсов бывают больше 10 микросекунд. Не говоря уже об механики аппарата, который будет осуществлять работу. Или это просто какая то идеальная система, разработанная 20 лет назад. Такое впечатление, что автор теоретик, пишущий очередную диссертацию, ради того, чтобы было. 20 монет в секунду, да ещё и с сортировкой фальшивых, это фантастика. Может быть нам автор всё же раскроет секрет, где работает двадцать лет данная система? И для чего её всё же решили усовершенствовать? А так всё это жесткий тролинг форума IMHO.
Парни, я же писал просьбу не писать подобные заявления. ЗАО НПП "АДОНИС". Продукция банковская техника - АСМ
https://promkat.ru/schetchik-monet.htm
который пока не знаю как посмотреть):
/путь к ИДЕ/hardware/tools/avr/bin/avr-objdump -S твой скетч.elf > xxxxx.S
где твой скечт.elf - елф-файл в tmp директории, где ИДЕ собирает проекты
xxxxx.S придумай имя файла для вывода и смотри свой ассемблерный код
asm volatile ("clr %0" : "=r" (Coin_OK));
clr очистка РЕГИСТРА! Не любой переменной. Риск архитектура, да ещ Гарвардская сильно отличается от привычного тебе асма ПК.
Я к тому, что вроде и пишу вместо Ардуиновских
вот так
Вот так и пиши. Это и правильно и оптимально. Если нет ситуации, критичной по времени - пользуйся digitalWrite(). Есть 100500 способов сделать ошибку в коде, не плоди новых. В твоей задаче НИГДЕ не нужно гнаться за тактами, честно. :)) Ты просто немножко неправильно подошел.
"При компиляции программы, если переменная не имеет глобального применения, компилятор часто размещает её в регистре. Тогда все меняется."
Про это я бы потом с Вами поговорил, мне это пока непонятно.
это значит, что для многих переменных компилятор вообще не отводит памяти. Если не указать этого специально.
Думаю, что понял Ваши мысли, спасибо за направления, буду пробовать.
И ещё, я мало знаю в ПК, программировал в Дельфи давно. А разницу работы switch | if-else, взял из сайта Гайвера
Гайвер - воплощенное ЗЛО! ;))))) Пример популяризация "себя любимого" без какого либо понимания предмета.
Пробую так:
не получается...
Какой пин Вы здесь выставляете?
Пробую так - acm volatile (" clr var");
Не получается...
Понятно.
как мне ...
Для начала, изучить систему команд процессора для которого пишете
основная проблема тут не в этом такте, а в сохранении и восстановлении регистров при входе выходе в прерывание. Местные аборигены не компетентно.
а можешь обьяснить - нафига конкретно в этом коде сохранять и восстанавливать регистры? Желательно честко и ясно, а не в стиле архата, который чуть что не так - включает обиженку
Потому как для исполнения кода обработчика прерываний процу нужно где-то хранить данные. Это "где-то" в первую очередь регистры. Но на момент прерывания эти регистры могут использоваться в соответствии с кодом основной программы. Чтоб их не затереть своими данными при вхождении в обработчик их запихивают в стек командой PUSH, а это 2 такта, а по завершению возвращают из стека обратно РОР, тоже 2 такта. Потому в идеале надо чтоб обработчик использовал минимум регистров и не сохранял лишних. Компилятор обычно этими 2-я вопросами не сильно озабочен. Потому и имеем вход+выход в пустое прерывание под 80 тактов. Но Ахат , помница, говорил что ему там че-то удалось сокращать. Если не врал конечно.
А конкретно этот код не выглядит нуждающимся в большом числе регистров для обработчика, обработчик в общем легкий. Потому наверняка удастся освободить хоть пару-тройку регистров, а это десяток тактов свободных. На фоне того одного такта, что ТС искал в начале темы - целое море ресурса )))
Проще.) Компилятор сохраняет в стеке, используемые в функции прерывания, регистры. Если в функции прерывания используются и другие (дополнительные) функции (компилятор не может определить используемые регистры), тогда контекст сохраняется по максимуму, т.е. все регистры!
тогда контекст сохраняется по максимуму, т.е. все регистры!
Ну такое я тоже видел, но в более новых версиях лучше, он сохраняет только нужные. Если б еще и оптимизировал код для сокращения используемых регистров! А че воще значить "компилятор не может определить используемые регистры" - ему все исходники дали, как так "не может"? Он плохо старается... Может речь о линковке с библиотечными функциями?
DEL.
Если временные параметры системы известны, можно попробовать отказаться от прерывания, а следить за датчиком в основном цикле.
Ленится.) А может, в этой функции, там ещё тыща функций вызывается?) Нет, компилятор не может видеть содержимое вызываемой функции, он видит только прототип, а функция может быть вообще в другом модуле.
Продолжу по своей теме. Согласен с тем, что нужно и можно увеличить период вызова прерывания Таймера до 20-30 мкс. Это конечно всё равно мало. Нужно оптимизировать уже написанный код основной программы. В ней тоже происходит какая-либо работа - опрашиваются микровыключатели на размыкание цепи (при размыкания происходит немедленный останов), опрашивается сенсор дисплея. Сенсор понятно, можно опрашивать 10-20 раз в секунду, биты в регистре выставлять вручную и через прерывание, а не функцией analogRead() выявлять факт касания сенсора, но когда идет его опрос, то всё равно этот опрос пересекается с работой датчиков. Микрики через простые опросы пинов опрашиваются быстро. Но мне всё равно хотелось бы, так как и интересно и нужно (придёт и к этому моменту) попробовать написать ассемблерные вставки. Запись в порт МК через ассемблер я понял как
У меня вопрос по чтению портов, такая инструкция не подходит
Чтение-запись Си-шных переменных, регистров МК (например я не знаю как прочитать регистры Таймера TCNT5H, TCNT5L) также не могу прочитать...
Вроде много где почитал, но конкретных примеров нет.
Прошу помощи, ребята, подскажите синтаксис.
Ленится.) А может, в этой функции, там ещё тыща функций вызывается?) Нет, компилятор не может видеть содержимое вызываемой функции, он видит только прототип, а функция может быть вообще в другом модуле.
Исходники ее есть в проекте, я вижу, мой кот видит, а компилятор не может... Если оставить в стороне случай, когда функция заранее скомпилирована в библиотеку, линкуется к проекту и ее исходники отсутствуют, то таки да, он плохо старается...
Запись в порт МК через ассемблер я понял как
Ну так уж зарубатся особого смысла нет, кроме особых случаев*. PORTB|=0x01 скорей всего и станет "sbi %0, 0x01".
Я в случаях средней сложности просто дизассамблирую что получилось и смотрю на сколько все плохо. Для этого в батнике держу что-то типа "c:\Program Files (x86)\Arduino\hardware\tools\avr\bin\avr-objdump.exe" -d sketch_sep18a.ino.elf >dtmf.asm . Файл *.ino.elf из папки build
Для побитовой работы с портами пользуюсь bitSet, bitClear, bit_is_set, bit_is_clear Обычно все норм при использовании констант.
*Про особый случай. WS2812b. Написал на ассемблерной вставке - работает. Переписал на си, контролируя чтоб получилось похожим как на асм - работает. Поменялись настройки - не работает, поменялась версия ИДЕ - не работает, поменялась фаза луны - не работает. Вытер нах, оставил на ассемблере.
Прошу помощи, ребята, подскажите синтаксис.
Помогаю: это не нужно делать ассемблерными вставками. Они НЕ ускорят работу программы.
Использовать в таких случаях ассемблер - просто мастурбация, т.е. занятие безусловно приятное, но совершенно бесплодное. Ассемблер применяют в критичных по времени исполнения кусках и те программисты, которые реально могут написать код, более быстрый, чем компилятор. Пример: библиотека fastled, вероятно единственный в Ардуино пример оправданного использования ассемблера.
Если ты просто хочешь изучить использование вставок, то это дело благое, но к твоей задаче отношения не имеет. Устанавливать переменную или писать в порт ассемблером - вообще чушь! Эти операции компилятор всегда сделает лучше сам.
Я же научил тебя получать листинг на асме? Так сам посмотри, что и как делает компилятор.
---------------
Еще раз и последний, если продолжишь ипать свое безумное прерывание - дальше участваовать не стану. Я написал тебе, что ожидать прекращения импульсов в прерывании - дикий бред. Настаиваешь на своем? Ок, но уже без меня.
Для начала, изучить систему команд процессора для которого пишете
почитал, но там я не понял как можно сделать чтение-запись Си-шных переменных через ассемблерные инструкции
Прошу помощи, ребята, подскажите синтаксис.
Неправда, если бы Вы действительно просили помощи, Вы бы отвечали на вопросы, которые Вам задают.
Я Вас как человека спросил
Пробую так:
не получается...
Какой пин Вы здесь выставляете?
Где ответ?
Также я дал Вам ссылку на руководство по машинным командам. Судя по дальнейшему, оно не прочитано. Так какая помощь Вам нужна, если Вы сами не работаете?
Теперь вот Вы пишете
Что значит "не проходит"? МК взрывается, гаснет свет во всём доме, или что? Что именно идёт не так?
Во-первых, приводите полный скетч, который я мог бы сам запустить и посмотреть.
Во-вторых, потрудитесь внятно объяснять что у Вас не работает. У меня, например, такая инструкция "проходит" на ура! Значит, у Вас что-то другое "не проходит" в той части скетча, которую Вы не показали.
Вроде много где почитал, но конкретных примеров нет.
Не знаю, что и где Вы читаете (Вы, правда, задолбали своими совершенно неконкретными "не проходит", "много где" и т.п. Выражайтесь конкретнее и яснее).
Правильно читать фирменную документацию. Но, если Вы не умеете читать (на языке оригинала), то попробуйте, например, вот этот пост - вполне адекватное изложение с херовой тучей примеров.
например, вот этот пост - вполне адекватное изложение с херовой тучей примеров.
Женя! Я уже дал ссылку на этот текст в №47.
Еще раз и последний, если продолжишь ипать свое безумное прерывание - дальше участваовать не стану.
Так, это шантаж. AlexBajdin59rus, не поддавайся. Ему все равно делать нехер, за кордон не выпускает и он тут тусить будет.
Еще раз и последний, если продолжишь ипать свое безумное прерывание - дальше участваовать не стану.
Так, это шантаж. AlexBajdin59rus, не поддавайся. Ему все равно делать нехер, за кордон не выпускает и он тут тусить будет.
Понял! :-)
Женя! Я уже дал ссылку на этот текст в №47.
Судя по реакции ТС на наши ссылки, и в третий раз давать придётся :-)
Прошу помощи, ребята, подскажите синтаксис.
Неправда, если бы Вы действительно просили помощи, Вы бы отвечали на вопросы, которые Вам задают.
Я Вас как человека спросил
Пробую так:
не получается...
Какой пин Вы здесь выставляете?
Где ответ?
Также я дал Вам ссылку на руководство по машинным командам. Судя по дальнейшему, оно не прочитано. Так какая помощь Вам нужна, если Вы сами не работаете?
Теперь вот Вы пишете
Что значит "не проходит"? МК взрывается, гаснет свет во всём доме, или что? Что именно идёт не так?
Во-первых, приводите полный скетч, который я мог бы сам запустить и посмотреть.
Во-вторых, потрудитесь внятно объяснять что у Вас не работает. У меня, например, такая инструкция "проходит" на ура! Значит, у Вас что-то другое "не проходит" в той части скетча, которую Вы не показали.
Вроде много где почитал, но конкретных примеров нет.
Не знаю, что и где Вы читаете (Вы, правда, задолбали своими совершенно неконкретными "не проходит", "много где" и т.п. Выражайтесь конкретнее и яснее).
Правильно читать фирменную документацию. Но, если Вы не умеете читать (на языке оригинала), то попробуйте, например, вот этот пост - вполне адекватное изложение с херовой тучей примеров.
Не знаю как вы много раз цитируете...
1) Да, действительно я тогда был не прав, на тот момент не знал синтаксиса этой инструкции. Я тогда хотел выставить 3 бит, а выставлял нулевой и первый.
2) Видимо немного запутался и не то написал. Инструкция проходит, да, а вот дальнейшие действия неправильные.
3) А что, нужно конкретные ссылки приводить...? Набираешь "ассемблерные вставки" в поисковике и они одни и те же у всех вылазят.
4) перечитываю ещё раз ТОТ ПОСТ вроде начинает доходить, стр. 8 этой книги вроде как пример для Си-шных переменных...?
Прошу помощи, ребята, подскажите синтаксис.
Помогаю: это не нужно делать ассемблерными вставками. Они НЕ ускорят работу программы.
Использовать в таких случаях ассемблер - просто мастурбация, т.е. занятие безусловно приятное, но совершенно бесплодное. Ассемблер применяют в критичных по времени исполнения кусках и те программисты, которые реально могут написать код, более быстрый, чем компилятор. Пример: библиотека fastled, вероятно единственный в Ардуино пример оправданного использования ассемблера.
Если ты просто хочешь изучить использование вставок, то это дело благое, но к твоей задаче отношения не имеет. Устанавливать переменную или писать в порт ассемблером - вообще чушь! Эти операции компилятор всегда сделает лучше сам.
Я же научил тебя получать листинг на асме? Так сам посмотри, что и как делает компилятор.
---------------
Еще раз и последний, если продолжишь ипать свое безумное прерывание - дальше участваовать не стану. Я написал тебе, что ожидать прекращения импульсов в прерывании - дикий бред. Настаиваешь на своем? Ок, но уже без меня.
1) Занятие конечно приятное, помню, давно пробовал писать программу на Асме для PIC-контроллера.
2) Попробовать -то надо... :-)
3) Fasted нашел, буду смотреть (она и ТОЙ ССЫЛКЕ есть)
4) Листинга на Асме нет в ПлатформИО, там только бинарный код... :-( Нужно попробовать через АтмелСтудио посмотреть...
5) Всё таки буду пробовать ожидать окончания импульсов в прерывании - какие-то минимальные сравнения делать там и оттуда получать выводы (или булевую переменную о выявлении сурогата)
Спасибо! Но за помощью я к тебе ещё обращусь ;-)
Тем более, что судя по:
моя просьба
проигнорирована.
Ну, тогда разбирайтесь сами. Удачи Вам.
Какие-то "обидки" у Вас... Я написал код, прокомментировал что выводит при этом коде, если человек знающий, сразу поймёт в чём дело. Зачем запускать код у себя, если вот он? Я понимаю что тут то-ли адрес, то-ли смещение, то-ли ещё чего (совсем другой регистр из 32-х ассемблерных) передаётся в порт, но как это исправить пока не в моих силах, знаний и опыт ане хватает. Так нет, чтобы подсказать, пишете ерунду...
Давайте писать по существу и не более, зачем хламить мою ветку форума
Какие-то "обидки" у Вас...
Какие? Вы, видимо, можете разбираться в программах не запуская их, а я - нет. Не умею я так. Поэтому я предложил Вам работать самому и пожелал удачи. Никаких обидок!
Я написал код, прокомментировал что выводит при этом коде, если человек знающий, сразу поймёт в чём дело. Зачем запускать код у себя, если вот он?
Вы точно уверены, что Вы лучше меня знаете, что и как мне делать, чтобы дать Вам дельный совет? Ну, если Вы знаете лучше, то и разбирайтесь сами, я Вам уже сказал.
Я, видимо, не "человек знающий", а потому, мне для того, чтобы Вам что-то точно подсказать, нужно:
Я всегда так делаю. Если Вас такой подход не устраивает - продолжайте без меня.
Давайте писать по существу и не более, зачем хламить мою ветку форума
Так вроде я с Вами уже попрощался, пожелал Вам удачи и не собирался её более захламлять. Это Вы зачем-то захламляете.
ТС Как по вашему происходит вывод переменной в порт или чтение из порта ???
ТС Как по вашему происходит вывод переменной в порт или чтение из порта ???
Чтение из порта, как я понял,
а вывод командой
Но почему я подставляя VAR
не могу отправить значение? Вернее отправляется значение отличное от значения, находящегося в VAR.
Берем простой код:
Компилируем и смотрим где лежат результаты компиляции:
...
При выводе сначала читается в регистр содержимое ячейки памяти, где лежит var и потом содержимое этого регистра выводится в порт.
При чтении сначала читается значение из порта в регистр и потом содержимое регистра выводится в ячейку памяти, где лежит var.
ГДЕ ЭТО У ВАС ???
Как видно из листинга - НИЧЕГО ЛИШНЕГО для данного простого кода компилятор не придумал !
Вам тут что бесплатные курсы inline assembler ? Вы с обычным assembler сначала разберитесь ! А еще лучше для начала понять логику работы процессоров !!!
ВО!!! Спасибо! Вот дельная помощь! Сейчас буду разбираться, с этими всеми _SFR_IO8, _MMIO_BYTE, __SFR_OFFSET!
Прямо пальцем ткнули что куда!
При выводе сначала читается в регистр содержимое ячейки памяти, где лежит var и потом содержимое этого регистра выводится в порт.
При чтении сначала читается значение из порта в регистр и потом содержимое регистра выводится в ячейку памяти, где лежит var.
ГДЕ ЭТО У ВАС ???
Как видно из листинга - НИЧЕГО ЛИШНЕГО для данного простого кода компилятор не придумал !
Вам тут что бесплатные курсы inline assembler ? Вы с обычным assembler сначала разберитесь ! А еще лучше для начала понять логику работы процессоров !!!
Да, но у меня же в переменную значение правильно записывается... А про запись значения в порт, я получается правильно предполагал, что не значение из переменной записывается, а что-то другое...
Да, но у меня же в переменную значение правильно записывается... А про запись значения в порт, я получается правильно предполагал, что не значение из переменной записывается, а что-то другое...
Уверены что правильно ??? Нельзя ничего записать в переменную без команды записи в память. Попробуйте значение отличное от дефолтного нуля и увидите. Команды IN OUT работают с регистром, а не с памятью !
http://www.gaw.ru/html.cgi/txt/doc/micros/avr/asm/start.htm
Рано Вам еще в assembler !!! В inline assembler я сам вникал долго, хотя обычный assembler я использую 30+ лет ...
Учиться никогда не поздно! Раньше я писал на асме для PIC. Знания минимальные остались. Теперь будем учиться большему! Так что больше позитива! :-)
Странные вольности он себе позволил ! Вместо одной команды - вставляет две !
Странные вольности он себе позволил ! Вместо одной команды - вставляет две !
А как "он" знает, что VAR находиться по адресу 0x800106? Это прописывается на этапе компиляции, сборки (не знаю точно правильное определение процесса)?
И я не понял:
тут мы хотим записать значение из переменной в порт, что делается инструкцией:
Почему после неё присутствует инструкция:
Или это ваш код, который я не видел?
Я правильно Вас понял, что в этом странность, двух инструкций?
Компилятору то не знать где он придумал хранить переменную ???
Это Ваш код так разворачивается в листинг и во втором случае он не верный !
Вот так должно выглядеть, что бы правильно работало !
чтение PORTB в переменную var:
запись переменной var в PORTB:
Вам тут что бесплатные курсы inline assembler ?
а хотелось! Как у Питера Нортона ...
Странные вольности он себе позволил ! Вместо одной команды - вставляет две !
;)))))) Если троллишь, то малацца!
Но если и правда не не знаешь, в чем собачка порылась, то беда. :((
Мужики, ну ведь среда только!
Вот и пятница пришла...
Но тема полезная, всё ж таки.)
wdrakula А ну как просвети ... чего это во втором случае он не справился ?
wdrakula А ну как просвети ... чего это во втором случае он не справился ?
нужно переменную в выходных указать. Компилятор грузит входные ДО кода, и выгружает регистры в ВЫходные после кода. Показать? Думаю уже сам понял. Во второй вставке переменная, как и в первой, указана входной, а первом блоке. А нужно через запятую во втором.
;))))
Там немного логика "черезжопная" в синтаксисе вставок, потому я ТС-у и не советую этим заниматься.
Меня другое озадачило - просили одну команду на два опкода - получили две на шесть опкодов.
ИМХО он должен был ругнуться, а не пытаться поправить ситуацию.
В первом блоке выходные, во втором входные ...
это работа с переменными, при вставках нужно же загрузить и выгрузить их. Там, чтобы выиграть такты, нужно ну вот-прям очень хорошо знать, что ты делаешь. ;)) Чаще всего это нужно для очень точного "ногодрыга" потому я и написал, что (ИМХО) единственное оправданное применение, которое я видел - библиоетка fastled.
Про мой ногодрыг забыли - http://arduino.ru/forum/programmirovanie/programmnyi-i2c-1-mgts-dlya-16-mgts-ustroistv-dlya-avr
Попробовал немного "поизвращаться" ...