Введение в продолжение
В предыдущей статье мы начали разработку программы для МК некоторого устройства. Мы получили первую версию алгоритма работы этого устройства записанного в С-подобном стиле. Также мы провели анализ этого алгоритма и наметили проблемы и/или ошибки которые нужно решить, а также некоторые теоретические вопросы связанные с общим пониманием построения алгоритма, такие как идентификация состояний и сущностей которым эти состояния принадлежат.
Мы пишем первую версию (и все промежуточные версии) программы не для того чтобы решить поставленную заказчиком программы (устройства) задачу! Сначала мы должны убедиться, что задача правильно (корректно) сформулирована и мы ее правильно поняли! Поэтому первая задача которую мы решаем и, соответственно, должна решать наша программа это демонстрация возможностей и идентификация, измерение параметров, значения которых и будут составлять окончательные технические требования к устройству и его встроенной программе.
Разработка ПО тем более встроенного ПО всегда включает в себя этот этап верификации и уточнения задания. Будь оно сформулировано по всем правилам как техническое задание или выражено в виде некоторых пожеланий-предположений о требуемой функциональности устройства оно все равно требует проверки РЕАЛИЗУЕМОСТИ (feasibility по английски).
Уточнение понимания работы управляемой системы, ловушка очевидности
Когда мы начинаем изобретать алгоритм управления некоторой системой, мы исходим из некоторого понимания внутренних принципов функционирования этой системы. Самой большой проблемой является нежелание проверить свое понимание этих принципов, которое обычно, мотивировано вот так:
Система работает вот таким образом <здесь приводится "очевидное" описание>, потому что по другому сделать нельзя!
По крайней мере мой опыт показывает что никогда нельзя опираться на непроверенное представление о работе системы подлежащей управлению. Это значит что мы не только должны иметь представление о работе системы, которой мы собираемся управлять. Мы должны придумать, как проверить это свое представление!
В случае с этой задачей я как раз попал в такую ситуацию, когда мое исходное представление о способе управления реле прерывателя оказалось неверным.
Я исходил из того что реле прерывателя включается только с подрулевого рычага, и поэтому параллельный ключ, который добавляет наше устройство не включит прерывания! Переключение режима работы прерывателя изображено на Рис.1.
Это неверное предположение достаточно сильно повышало сложность алгоритма управления, который я пытался реализовать-запрограммировать в предыдущей статье. Более того ошибки которые я нашел анализируя написанную версию алгоритма в большинстве своем потеряли актуальность после изменения представления о способе работы управляемой системы, хотя способ построения алгоритма остался прежний (код в конце статьи).
В конце концов я выяснил у автора задачи что прерыватель включается при появлении напряжения в цепи ламп поворотников (любых). То есть замыкание дополнительного ключа также включает прерыватель! Рис.2
Про состояния или проблема терминологии
В нашем случае можно определить:
- состояние управляемой системы (назовем его RLC) которое МК получает ввиде 3-х битного слова по линиям inL, inR, inC;
- состояния устройства которое МК формирует на выходах outR, outL;
Дело в том что вот это значение RLC кодирующее положение подрулевого рычага мы взяли и обозвали «состоянием», а это тянет и уводит нас в какие-то высшие сферы, дальние дали абстрактных мат.теорий о машинах состояний. Я предлагаю забыть слово состояние, и обзывать это значение «параметром» нашего алгоритма, это позволяет сосредоточиться на практических аспектах решения конкретной задачи!
Тем более есть глобальные состояния устройства, которые действительно важно различать:
- это активное состояние устройства, когда устройство замкнет хотя бы один дублирующий ключ, таким образом обеспечивая вмешательство в работу управляемой системы(активность проявляя), и
- пассивное состояние – это ожидание активирующего устройство события (событие включения поворотника здесь).
Код с состояниями
Автор задачи выложил здесь вот такую реализацию управляющей программы с использованием состояний.
Я бы main() написал вот так:
uint8_t rlCode; uint8_t rlCodeSaved; uint8_t count_pause; int main(void) { port_ini(); //инициализация портов StartPoint: //начинаем с отключенными ключами устройства: PORTB &= ~((1 << RIGHT_OUT) | (1 << LEFT_OUT)); count_pause = 0; do{//ждем нажатия поворотника: rlCode = get_code(); } while (!(rlCode == command_Right || rlCode == command_Left)); //устройство переключается в активное состояние-замыкает дублирующий ключ, //который закодирован в rlCode: if (rlCode == command_Right) { PORTB |= (1 << RIGHT_OUT); } if (rlCode == command_Left) { PORTB |= (1 << LEFT_OUT); } //мы продублировали замыкание цепи от подрулевого рычага! rlCodeSaved = rlCode; while (1)// цикл отсчета продленного кол-ва "морганий поворотника" {//--->LOOP do{//ждем переключения поворотника или паузу от прерывателя rlCode = get_code();//дребезг=command_Error тоже игнорируем: //мы продублировали замыкание цепи от подрулевого рычага, поэтому //если рычаг вернется в нейтральное положение условие все равно будет выполняться: } while (rlCode == rlCodeSaved || rlCode == command_Error); if (rlCode != command_Pause) goto StartPoint; count_pause++;//считаем разы продленного "моргания" на каждой паузе! if (count_pause == TURN_COUNT) {//rlCodeSaved хранит инфу о том какой поворотник мы включили! //но мы можем просто выключить оба здесь! PORTB &= ~((1 << RIGHT_OUT) | (1 << LEFT_OUT));// отключили дублирование // NO goto StartPoint; теперь мы должны дождаться когда отключат этот поворотник! //чтобы повторно не включить удлиннение нажатия поворотника } do{ rlCode = get_code();//ждем окончания паузы } while (rlCode == command_Pause || rlCode == command_Error); //возвращаемся в //начало циклА отсчета продленного кол-ва "морганий поворотника"--->LOOP } }
Идеальная программа???
Я наверно всех разочарую, потому что в конце концов вынужден написать банальную вещь:
Идеальной программа станет только тогда, когда будет проверена ее стабильная работа в реальном "железе", и ее работа удовлетворит пожелания заказчика разработки устройства, в какой бы форме эти пожелания не выражались!
К счастью я придумал второй критерий идеальности, который кажется на совсем банальный:
Идеальная программа должна легко (то есть достаточно быстро) позволять удовлетворять вновь возникающие пожелания заказчика (в ограниченной области функциональности, конечно), неизбежно возникающие от прикосновения ко вновь обретенным возможностям автоматизации его скучных-нудных операций :) !
Мне кажется мой вариант программы удовлетворяет обоим этим критериям!
Комментарии (73) | Я собрал (0) | Подписаться
Для добавления Вашей сборки необходима регистрация
[Автор]
Особенно полезным был режим симуляции программ в процессоре. Очень помогает понять работу процессора.
Но 32-е процессоры и программы на них уже слишком сложны для симуляции.
[Автор]
[Автор]
А для компиляции какая разница сколько ему ресурсов надо???
У меня комп хороший я не замечаю недостатка.
Или вы про что???
[Автор]
...
while(1)//придется добавить этот цикл!
{
StartPoint:
//начинаем с отключенными ключами устройства:
PORTB &= ~((1 << RIGHT_OUT) | (1 << LEFT_OUT));
count_pause = 0;
do{//ждем нажатия поворотника:
rlCode = get_code();
} while (!(rlCode == command_Right || rlCode == command_Left));
if (rlCode == command_Right) { PORTB |= (1 << RIGHT_OUT); }
if (rlCode == command_Left) { PORTB |= (1 << LEFT_OUT); }
rlCodeSaved = rlCode;
while (1)// цикл отсчета продленного кол-ва "морганий поворотника"
{//--->LOOP
do{//ждем переключения поворотника или паузу от прерывателя
rlCode = get_code();//дребезг=command_Error тоже игнорируем:
} while (rlCode == rlCodeSaved || rlCode == command_Error);
if (rlCode != command_Pause) break;
count_pause++;//считаем разы продленного "моргания" на каждой паузе!
if (count_pause == TURN_COUNT)
{//rlCodeSaved хранит инфу о том какой поворотник мы включили!
PORTB &= ~((1 << RIGHT_OUT) | (1 << LEFT_OUT));// отключили дублирование
}
do{
rlCode = get_code();//ждем окончания паузы
} while (rlCode == command_Pause || rlCode == command_Error);
}
}
...
Скорее наоборот - руководство, как не надо писать программы.
Например, подобные вещи:
И если скажете что это придирки, то что скажете на этот шедевр:
do{//ждем нажатия поворотника:
rlCode = get_code();
} while (!(rlCode == command_Right || rlCode == command_Left));
Ждать пока rlCode правильным не станет? Переписывать всю логику? Легко модидифицируемая, ничего не скажешь.
В статье есть ссылка на код (с автоматом состояний), вот там уже вполне зрелая программа. А эту под таким заголовком
я бы постеснялся даже показывать
[Автор]
А чем вам не нравится ШедевР в трех строчках, не совсем понятно, зачем из этого цикла выходить что бы
[Автор]
И благодарю за анализ!
[Автор]
[Автор]
Исходный "автомат состояний" также "завешивает программу в этих местах"!
Только при таком оформлении "эти места" четко видно, видна их последовательность, а в СВИЧЕ последовательность произвольная, никак не связана с реализуемым алгоритмом.
Эта произвольная последовательность это не гибкость, это бардак!
В каком месте автомат завешивает программу? Автомат является частью вечного цикла, а не вечным циклом, например:
...
while(1)
{
state_machine();
//другой код, который выполнится
//в любом случае, независимо от состояния
//автомата
...
}
Эта произвольная последовательность это не гибкость, это бардак!
[Автор]
Вернетесь, поговорим :) !
Т.е. неважно
case RIGHT: //горит правый поворотник
...
case LEFT: //горит левый поворотник
case LEFT: //горит левый поворотник
...
case RIGHT: //горит правый поворотник
[Автор]
Вот вы даже состояния не корректные процитировали, не нужно разделять там RIGHT, LEFT, потому что достаточно знать что включилось, а что включено возвращается из предыдущего состояния "ВЫКЛЮЧЕНО" с выходом из этого предыдущего состояния.
То что надо выполнять "всегда", обычно пишется в прерываниях, приведите пример, я смогу ответить вам по существу этого примера!
А пока вопросы (или просто заявления?) не по существу, ответы соответствуют!
Это плоды "идеального" подхода к завешиванию.
[Автор]
И еще раз повторяю, если для вас не важен порядок перехода между состояниями или он вообще для вас не существует, то разговаривать не о чем :( !
Счастливого пребывания вам, на "определенном уровне абстрактного видения задачи"!
Есть конкретная статья, с конкретным кодом и громким названием. Код не соответствует вами же предложенному критерию, поскольку полностью выключает из работы основной вечный цикл, и вместо того, чтобы в будущем при необходимости просто добавить новую задачу в тело while(1), придется трогать (и не слабо) код уже написанной и отлаженной задачи. Более того, код противопоставлен добротному коду на автомате состояний (отсюда такое внимание автомату) Так понятно?
[Автор]
Может вам легче совсем сайт закрыть, что бы он не раздражал вас чужими мнениями, отличными от вашего?
Вы запросто определяете что тот код добротный, а этот на О, я рад за вас!
Но я так пишу уже больше 10 лет, и точно знаю что если вы действительно попробуете добавить какое то расширение функциональности, в эти два варианта программы, то в моей версии это будет проще и абсолютно прозрачно по отношению к закодированному "порядку перехода между состояниями", которого в ваших CASE-ах надо еще поискать!
[Автор]
Вы всегда должны проверить при добавлении новой функциональности, что эта новая функциональность не мешает правильному переходу между состояниями!
А если вы добавляете новое состояние как вы ищете все состояния из которых будет переход в это новое состояние, и все те в которые будет выход из этого нового состояния, попробуйте ответить?
А еще как добавить разные действия для переходов из одного состояния в разные? А если должно быть определенное действие при входе в данное состояние?
Что важнее иметь возможность реализовать все эти варианты или иметь одну возможность
"добавить новую задачу в тело while(1)"
[Автор]
Я только могу вас заверить что если писать блоками:
[-действие перед входом в состояние-]
do{
[- и пожалуйста здесь вызов общей для всех состояний функции-]
[-периодическое-постоянное действие в состоянии-]
}while(условие выхода)
[-действие после выхода из состояния-]
[-логика выбора следующего состояния-]
Мог бы показать это на примерах, но это книга получится, а не статья, а я со статьей уже утомился :) !
[Автор]
get_code()
вызывается в каждом состоянии, то есть ВСЕГДА(как вам и хотелось!), как то что в вашем подходе вы пишите перед свичом! Добавляйте туда все что вам нужно ВСЕГДА!
Расширяй - не хочу, что называется!
[Автор]
Потом неужели трудно догадаться что не обязательно внутрь get_code() все пихать, есть же места откуда вызываются все функции get_code(), там было:
[- и пожалуйста здесь вызов общей для всех состояний функции-]
Но проект интересный, ЮСБ программно реализован на ассемблере, я попробую расковырять на досуге!
[Автор]
Так вот СВИТЧ с состояниями (которые вы видимо имели ввиду) там убрался в одной функции, а вся основная работа к нему, в общем то, отношения не имеет.
Препроцессор повсеместно использовать тоже не есть хорошо. Препроцессор создает поля в глобальной области, как вследствие все будет сидеть в куче-перерасход ОЗУ.
[Автор]
если в виде
#define PIN_ENABLE(x) PORTB|=(1<<x)
#define LEFT_ENABLE PORTB |= (1 << LEFT_OUT)
[Автор]
ДЫк вот эта программа и реализует логическую функцию (более сложную чем элемент И)!
Тут в зависимости от состояния 3-х входных ног/битов меняется состояние 2-х выходных ног/битов.
Вопрос во времени срабатывания этого элемента логической функции,
5 микро секунд допустимая задержка???
например.
[Автор]
Потом на Xmega -х, кажется, можно такую логику как аппаратную между пинами сконфигурировать для этих целей.
И Интересно было бы узнать исходную задачу, а не то чем вы ее собираетесь решать.
[Автор]
loop1:
in reg1, porta; 1-tick
in reg2, portb; 1-tick
and reg1, reg2; 1-tick
right-shift reg1; 1-tick
out portB, reg1; 1-tick
rjmp loop1; 2-tick
На частоте 16 MHz это 7/16 секунды(7 ticks), входы элемента И должны быть на одинаковых пинах условных портов A and B, а выходы след. По старшинству биты-пины
Про одновибратор надо уточнить длину импульса и запускающее событие, можно на счётчике сделать аппаратно-парралельно.
А выход из цикла, для какой то настройки например, по прерыванию от кнопки!
[Автор]
[Автор]
Только сейчас посмотрел про Attiny13, у нее же только 6 ног доступно! Это ж получается только на два элемента И хватит! Про одновибратор тогда совсем не понятно!
И цикл надо переделать на использование только одного порта. (Я там кстати ошибся: in regx, PINx - должно быть, а right-shift это LSR)
В общем на ассемблере можно написать «два элемента И» со временем срабатывания не более 1.5мкс (может даже до 1мкс, ТОЧНО можно!) даже на 10МГц при произвольном расположении ног на одном порту. Можно объявить конкурс для знатоков AVR-ассемблера :). Я бы мог передать запороленный ZIP с ответами, тому кто может предложить свой вариант.
[Автор]
Это прямое управление аппаратными возможностями процессора, С для этого не предназначен, по определению!
Это вопрос знания-понимания аппаратных возможностей контроллера (периферия, конвеер, шины) и возможностей управления ими (регистры, ассемблер). К языку-С это отношения не имеет.
Потом на ассемблере это и проще - это всего пара десятков инструкций!
Потом это у вас все таки более сложный элемент - не просто И, одновибратор с разрешением ?!
Надо точно требования по временам выписать:
вход разрешения - тут2(!) максимума по задержкам туда и обратно, а может и не 2 а больше + в зависимости от состояния входа одновибратора;
вход включения импульса если пропадает 1 - выходной импульс тоже снимать? или он уже не влияет 200мс, срабатывать по фронту(через какое время фронт фиксировать?) или по импульсу(какой минимальной длинны?)
...
Обычно диаграмму входных-выходных сигналов рисуют с указанием максимальных/минимальных времен.
Тоже можно от руки!
[Автор]
Там еще надежность падает с количеством корпусов, соединений, паек, сложность-цена изготовления растет и т д, и т п
Да напишу я вам 20-40 инструкций, нарисуйте диаграммы!
[Автор]
И, вообще то, должно быть 3-сигнала, а у вас почему то четыре.
1) - сигнал, я так понимаю разрешение, правильно?
Должно быть штуки 4 - диаграмм, не меньше!
Можно в личку.
[Автор]
И, все таки, нужно знать допустимое время задержки срабатывания, если in1 переключается в ОДИН какая максимальная задержка допустима до реакции out1, out2? Скажем 3 мксекунды (это с запасом) устроит?
И наоборот когда in1 или in2, in3 переключаются в НОЛЬ, раз там И стоит, они переключают его выход в НОЛЬ, тот же Вопрос: какая максимальная задержка допустима до реакции out1, out2? Скажем 3 мксекунды (это с запасом) устроит?
[Автор]
Я вижу:Power Supply Current
Active 8MHz, VCC = 5V
Max. = 12 mA
12 mA устраивают?
Active Mode:1 MHz, 1.8V: 240µA
[Автор]
По работе ЖМ еще вопросы будут:
он включается фронтом или уровнем?
То есть когда с ИЛИ приходит переключение с 0-ля в 1-цу ЖМ переключает выход в 1-цу на 200мс, а если за эти 200мс 1-ца пропадет у него на входе что он должен делать с выходом?
А если 1-пропадет и снова появится?...
А еще, если 200мс прошли, а 1-ца на входе не пропала, что у ЖМ на выходе должно быть?
[Автор]
А еще, если 200мс прошли, а 1-ца на входе не пропала, что у ЖМ на выходе должно быть?
Не слишком запутанно, можно понять?
[Автор]
получается что надо чтобы импульс любой длительности на входе1(например) укорачивался до 200мс,
то есть:
вход1 переключился из 0-ля в 1-цу;
выход1 переключился в 1-цу тоже (вход3 пока не рассматриваем - всегда 1-ца!);
если вход1 уйдет в 0-ль рагьше 200мс, оконечное И сделает 0-ль на вЫходе1 по любому!
а если вход1 остается 1-ца дольше 200мс, ты написал
то есть получается:
выход1 просто повторяет вход1 (вход3=1 напоминаю),
зачем тут ЖМ НЕ ПОНЯТНО поэтому!!!
[Автор]
значит пока на вход3 стоит 0-ль на обоих выходах должен быть 0-ль, не зависимо от значения на входах 1,2, правильно?
Тогда должен ли ЖМ запускаться когда вход3=0???
то есть:
вход3=0
вход1,2 переключились в 01/10/11 (выбирай!)
выходы1,2 остались 00???
через 50мс вход3 стал1-ца!
выходы1,2 переключаются в 01/10/11 на 200мс --ИЛИ-- на 150мс (потому что ЖМ запустился 50мс назад пока вход3 был =0)?????????????
[Автор]
Удлинить входной короткий импульс (на любом из выходов 1,2) он не может, потому что на выходе элементы-И стоят.
Если он должен обрезать длинные входные импульсы - так это как то не вяжется с вашими пояснениями.
Опишите хоть одну ситуацию, когда видно что ЖМ действительно работает (удлиняет импульс/фронт или он должен, все таки, обрезать или и то и другое?), когда видно что ЖМ действительно нужен!
Попробуйте рассуждать как я:
на вход-2 пришла 1-ца на 50 мс, на выходе 2 появится 1-ца на 50 мс, так? что будет через 50мс??? если эти 50мс теперь заменить на 350мс - какой сигнал должен быть на выходе-2???
Достаточно эти два варианта расписать, кажется!!!
[Автор]
дальше на вход-2 стоит 0-ль 50 мс, на выходе 2 0-ль на 50 мс, так?
снова на вход-2 пришла 1-ца на 50 мс, на выходе 2 опять 1-ЦА на 50 мс, так?
Ну никак не видно здесь необходимости применения ЖМ - схема будет так же работать, если его просто закоротить, кажется! Или я чего то не понимаю, что то упускаю??? Что?
Я понимаю что для исходной функции он, наверно, нужен! Но логику вы похоже не правильно нарисовали, по тому что в ней он смысла не имеет.
[Автор]
Напишите, все таки, исходную задачу (если секретно, то в личку, ни кому не расскажу!) зачем нужно устройство, а не ваше решение и я смогу предложить вам варианты решения: программные или на логических элементах или комбинированные,
-ИЛИ-
нужны диаграммы входных/выходных сигналов (как я и просил сначала)!
Потому что где гарантия что эта схема без ошибки?
Мне надо убедиться что задача корректно поставлена иначе нет смысла ее решать!
Кстати, у автора задачи по которой написана статья, схема сначала тоже секретная была, а потом он ее выложил, чтобы тип транзистора ему проверили-подсказали!
[Автор]
1.Движение в сторону закрытия должно быть запрещено если тумблер ВЫключен???
2.
3.
4.Управляющие сигналы это сигнал открытия(in1) и сигнал на закрытие(in2), который срабатывает(действует) только если ВКЛючен тумблер, правильно???
[Автор]
Если включение пришло на in1 и in2 одновременно,
(что то замкнуло, например, нештатные ситуации тоже подлежат рассмотрению!)
То есть имеем одновременно сигнал открыть и закрыть, КАК должен вести себя двигатель??? - вы должны определиться - это тоже важно и с точки зрения реализуемой логики и с точки зрения безопасности и/или минимизации ущерба от нештатной ситуации!
1) Движение в сторону закрытия должно быть запрещено если тумблер ВЫключен, но тумблер не влияет на открытие.
2) Датчик должен сигнализировать, если он сработал при замкнутом концевике 1.
3) Пока что концевик один, только в сторону закрытия, так как заклинивание может произойти при закрытие, но планируется как нибудь примастерить концевик и на открытие. Вы правильно поняли, концевик останавливает движени в свою сторону. 200 мс отсчитываются с момента прихода сигнала на открытие или закрытие.
4) Тумблер запрещает только закрытие, на открытие он не действует, что бы в нештатной ситуации всегда была возможность открыть.
[Автор]
судя по всему датчик и концевик это одно и то же, потому что и тот и другой выдают сигнал о закрытии, значит это одно и то же устройство!
Просто есть два варианта срабатывания датчика во времени относительно состояния тумблера:
1.тумблер ВКЛючен, через некоторое время происходит закрытие - это точно АВАРИЯ-запрещенное переключение!
2. включаем тумблер когда система УЖЕ находится в закрытом состоянии - вы должны сказать, выдавать здесь АВАРИю или это допустимая ситуация!!!???
Надо все равно, вам(!), определить приоритет!!!
Я подскажу, раз
И посчитаем сигналы:
1.тумблер
2.in1
3.in2
4.концевик закрытия (end)
5.концевик ОТкрытия (start- так он есть или будет :)
6.ВЫХОД: двигатель стоп или закрываем
7.ВЫХОД: двигатель стоп или ОТкрываем
Получается что бы убралось на 6-ть ног надо или
концевик ОТкрытия исключить все таки,
или
какую-то логику все же поставить
или
взять чип с большим кол-вом ног!!!
Аварию выдаёт срабатывание датчика передвижения пластины до размыкания концевика закрытия. т. е. если под какой то силой произойдёт открывание, концевик закрытия он же номер один остаётся включённым и должна сработать сигнализация об аварии. Это единственное условие.
На счёт приоритета Вы правы, но всё же лучше что бы процессор определял какой будет первым.
Я постараюсь установить концевик открытия, но не знаю насколько это возможно.
Я подумал на счёт количества ног и решил купить готовое устройство на АтМега88, но программу всё равно придётся писать.
[Автор]
А про датчик движения все равно не понятно - это еще один датчик?
Но, значит, в любом случае, еще один сигнал с датчика будет, он будет 8-ой.
А в общем, если это сигналы с механики, это все должно быть достаточно медленно, что бы на С-реализовать.
Всего 9 портов:
1) тумблер,
2) концевик закрытия,
3) концевик открытия,
4) датчик движения пластины,
5) вход сигнала закрытия,
6) вход сигнала открытия,
7) выход сигнала закрытия,
8) выход сигнала открытия,
9) выход сигнала аварии.
Думаю пока всё. Мы столько с Вами говорили, Вы можете привести хоть один пример для этого?
[Автор]
Если бы это был разговор а не переписка, он состоялся бы в течении полчаса, максимум час!
Это все еще самое начало, то есть мы только подходим к формулировке технического задания. И эта формулировка еще будет предварительная! Потому что,что бы выяснить технические параметры окончательно, надо написать программу которая позволяет их измерить и подтвердить или скорректировать.
После чего уже можно написать окончательную версию алгоритма!
Бывает конечно что можно попасть в цель с первой реализации. но вероятность достаточно мала, и лучше на это не рассчитывать!