Введение
На просторах форума мне попалась интересная задача на тему внешнего устройства расширяющего возможности световой сигнализации автомобиля, процесс решения которой показался мне очень показательно-поучительным в плане демонстрации процесса анализа исходных данных и, собственно разработки программного обеспечения микроконтроллера (ПО МК) на основе этого анализа.
По моим наблюдениям даже начинающие разработчики не испытывают больших проблем с разработкой схемотехники для своих разработок, а вот с разработкой программ для микроконтроллеров (встроенных вычислительных машин) часто возникают трудности, казалось бы, на ровном месте.
Я надеюсь что эта статья, как раз, будет интересна как наглядный пример формирования программы микроконтроллера на языке С из некоторого произвольного описания назначения устройства, описания его функциональности, схемотехники, внешних воздействий-сигналов… Надеюсь, что интересна будет не лишь начинающим разработчикам встроенного ПО, но и тем кто считает себя опытным программистом.
Постановка задачи
Изначально задача была сформулирована в виде достаточно развернутого вопроса написать некоторый код в этой теме, потом было вот это уточнение задачи где упоминаются в том числе:
Функциональные возможности устройства, устройство работает следующим образом:
• При коротком нажатии на рычаг поворотов – устройство выдаст серию запрограммированных импульсов мигания лампами поворотов;
• При длительном нажатии на рычаг поворотов (больше одного моргания) – эффекта удлинения не будет;
• При переключении из одного направления в момент удлинения поворотов в другое, повороты прекращают мигать по первому направлению и начинают мигать по второму.
В конце концов была приведена схема: Схема устройства подключенного к управляемой системе.
Анализ схемотехники
На исходной схеме даже не выделено устройство, работу которого надо запрограммировать, поэтому я приведу здесь дополненную схему рис.1 на которой схемотехника, которая относится к нашему разрабатываемому устройству выделена в рамке.
Я уверен, что автор задачи, совершенно справедливо, считал эту информацию очевидной, поэтому не делал соответствующих пометок на схеме. Но, дело в том, что при разработке ПО надо четко определить что мы имеем дело с новым устройством, которое подключено и некоторым образом воздействует на существующую систему световой сигнализации автомобиля (далее везде просто систему).
К примеру, когда мы рассуждаем о состояниях, мы должны четко различать состояния указанной системы и состояния нашего устройства, это очень важно! Про состояния я напишу подробнее позже.
Рис.1
Также я заменил изображения транзисторов на изображением абстрактного ключа и подписал ключи (ключ — элемент электрической цепи, выключатель), потому что тут важно понимать, что устройство добавляет соответствующие ключи (Доп. Ключ значит дополнительный) параллельно уже существующим в системе, и через них получает возможность управлять работой системы. И нам не важно, с точки зрения программирования/управления как реализованы эти управляемые ключи (транзисторы это, микросхемы, реле …)
Также подписаны входы (inL, inR, inC) через которые МК получает информацию о состоянии системы (о состоянии поворотников и реле прерывателя, которые в свою очередь отображают состояние или положение подрулевого рычага) и выходы управления ключами outR, outL.
Первая версия алгоритма
Теперь надо попробовать последовательно записать действия контроллера в ответ на некоторое изменение состояния системы, в нашем случае оно определяется состоянием подрулевого рычага.
Я предпочитаю это делать С-подобной записью или псевдо кодом.
Вот что у меня получилось с первого раза:
startPoint:
While(!(RLC = getRLC()) & 0b101)){};
cntrBitsSet(oldRLC& 1, (oldRLC >> 2) & 1);//замкнуть паралельные ключи
//в соответствии со значениями R and L!
timerStart();
oldRLC = RLC;
RLC = getRLC();
While(RLC == oldRLC || RLC == 0)
{ RLC = getRLC();};//включился поворотник – ждем и считаем время
Period = timerStop();
If(period > ShortTime) goto longTimeBranch;
ShortTimeBranch:
timerStart(3 seconds);
While(timerIsActive()) {};
Goto startPoint;
longTimeBranch:
cntrBitsClearAll();//РАзОмкнуть паралельные ключи
Goto startPoint;
Поясню подробно:
Как видим алгоритм наш замкнулся, вернулся в начало. Так же можно убедиться, что он не зависит от того, в какое состояние переключается рычаг из нейтрального-начального положения, остается исправить отмеченные замечания на следующих итерациях, но это уже тема следующей статьи где собственно и должен быть реализован идеальный алгоритм.
Я, честно говоря, хотел коснуться проблемы реализации счета не лишь времени, а количества периодов горения/гашения поворотников тоже, но материала уже и так с избытком, надо посмотреть будет ли интересна хоть кому то эта довольно специфичная тема.
Заключение
Как уже упоминалось это лишь первая версия алгоритма, и она конечно НЕ идеальная.
Умозрительное «выполнение» придуманного алгоритма разработчиком вместо МК позволяет нам сформулировать и подготовится к рассмотрению частных ситуаций, для которых эту версию алгоритма надо изменить и/или дополнить.
Уже сейчас можно видеть, что разработка алгоритма это процесс умозрительного моделирования действий процессора и реакции на него управляемой системы, то есть мы формулируем содержание операций, которые должен выполнять контроллер для контроля за состоянием «вверенного ему оборудования» и для управления этим оборудованием в соответствии с этим состоянием. А реализация кода этих операций эта тема для ещё одной статьи и это составляющая подведет нас уже к идеальной программе.
Надеюсь данная статья позволит осознать специфику и сложность реализации ПО для казалось бы примитивных устройств, таких как мы рассмотрели.