В предыдущей статье я затеял создание контроллера вторичных часов «Воронеж», и даже добился некоторой работоспособности. Однако так и не разгадал окончательно ребус формы и длительности передаваемого сигнала. На ту публикацию откликнулось большое количество людей, и предложило свою помощь. Кто-то помогал советом, кто-то снял живые осциллограммы с различных первичных часов, кто просто помог добрым словом и оказал поддержку. В результате понял, что нельзя бросить это устройство в стадии недоделки, потому что за ним следит такое количество людей. Поэтому было принято волевое решение довести всё до конца, при этом с максимальным качеством, доступным в домашних условиях. Главный девиз этой разработки был:
Делай хорошо — плохо само получится.В результате на вторую часть я потратил в полтора-два раза больше времени, чем на первую: причёсывал код, занимался механикой, чертил детали корпуса, печатал их на принтере, точил на токарном станке, а главное, страдал от перфекционизма. Как обычно бывает, механическая часть съела чуть ли не 40% всего времени. В общем, поехали, будет интересно: от идеи до законченного устройства.
Ребус сошёлся!
Как вы помните, ранее я так и не смог разгадать ребус формы сигнала, хотя был очень близок. Однако пользователь Gengenid буквально в первом комментарии разъяснил, что там да как, а потом и привёл эпюры верного сигнала. В тот момент я почувствовал себя лопухом, это было гениально и просто! (Плюсцов этому господину!)
После выхода статьи, буквально этой же ночью sfrolov прислал мне реальные сигналы с настоящих первичных часов, что подтвердило догадки Gengenid. И они меня просто потрясли! Смотрите на значения Δx:
Таким образом, если все эти, известные мне тайминги нанести на эпюру из документации, и исправить ошибки на ней, то получится следующий график:
Структура информационного сигнала.
Напомню аббревиатуры: СИ — синхроимпульс, который означает начало посылки, ТИ — тактовый импульс, 10 штук на каждую цифру, и ИИ — импульс, положение которого определяет, какое будет сейчас число. Проще говоря, сколько штук ТИ после ИИ — это и будет число. Посмотрите на картинку, на число 10 в начале: у 1 стоит всего 1 ТИ, а у нуля — ноль ТИ.
Даже передать не могу, какой это невероятный кайф, найти ошибку в старой документации и исправить её, даже если она уже никому не нужна.
А теперь минутка занимательной арифметики. Как вы понимаете, число 20 очень плохо делится на числа кратные тройке, поэтому будут периоды (они обозначены в скобках), но есть и положительная сторона, теперь всё превращается в банальную математику.
Посылок у нас шесть, значит интервал между посылками у нас будет:
Это было выяснено ещё в прошлой статье, а вот самое интересное, длительность фронта ТИ составляет
83,(3) мкс
(микросекунд). А значит, длительность всего интервала посылки у нас составляет:Вы же понимаете, что это значит? Длительность тактовых импульсов равна
20*83,(3)мкс
, а остальное время, также 20*83,(3)мкс
— это длительность паузы между посылками. А вся посылка занимает 240 минимальных импульсов:Это просто фантастически гениально, меня просто потрясла гибкость мысли советских инженеров, которые сделали такой простой и точный арифметический подбор. Теперь ясна тайна всей формы посылки. Главное, что программа упрощается в разы, подробнее об этом поговорим в разделе программы.
Электрическая часть
В предыдущей статье я кратко коснулся схемотехники электрической реализации устройства, но фактически всё это описал, как говорится, на словах. Поэтому для тех кто захочет повторить приведу схему. Итак, контроллер состоит из следующих узлов:
- Arduino uno
- Модуль дисплея LCD Keypad Shield
- Модуль часов реального времени ds1307
- Модуль управления двигателем L9110S (позднее заменён на микросхему IR4427)
Решил всё же предать ГОСТовские схемы, и освоил-таки программу fritizing, где все диайвайщики рисуют схемы. Тем не менее рисовать подробную схемотехнику смысла сейчас и вправду нет, если делаешь всё на модулях.
Блок-схема контроллера.
Также ранее, я сказал, что модули L9110S у меня горели как спички. Изначально микросхема IR4427 была в матраце с дырками, но было слишком много помех. При этом непонятно, они возникали из-за плохого контакта, или неверной схемотехники.
Осциллограмма зашумлённого сигнала.
На осциллограмме хорошо видны наводки 50 Гц (как раз попадают в период), а также переходные процессы в местах смены с нуля на единицу. Так называемый “меандр курильщика” (подробнее в моей статье, откуда он возникает).
Посмотрел на жменю горелых платок L9110S, на распиновку IR4427 и решил их поженить между собой. А чтобы минимизировать наводки, по совету моего товарища, сделал на выходе подтяжки к нулю (это рекомендовали комментаторы в прошлой статье).
Схема этого крокодила получилась следующая, умышленно оставил схематику второго канала, чтобы понимать что и где.
Обратите внимание, что микросхема перевёрнута. В жизни всё не так красиво выглядит, потому что, конечно же, по пинам не совпало, но всё же не так страшно.
Получившийся «крокодил».
Резисторы подтяжки.
На самом деле, я так решил сделать, потому что на плате были уже все необходимые конденсаторы и подтягивающие резисторы, и их не надо было распаивать заново. Конечно, сигнал всё равно не идеален, есть выбросы, и думаю их можно было бы убрать, поставив параллельно резисторам по маленькому конденсатору, но вроде работает и с ними.
Меандр курильщика.
Конечную плату сделал на макетке. В целом, считаю это плохим решением, но поскольку серию делать не планирую, то и так сойдёт. Если у вас планируется плат больше одной, всегда делайте печатную плату — это сильно убыстряет монтаж!
Его величество код!
Программа из предыдущего проекта была переписана практически полностью, остались какие-то общие принципы. Сам код очень огромен, и разумеется, тут его приводить нет никакого смысла. Вы можете целиком его оценить на гитхабе. Но, всё же хочу заострить внимание на двух ключевых моментах, которые я нахожу достаточно интересными.
Формирование сигнала
Возвращаясь к началу статьи, помним, что самый минимальный импульс у нас составляет
83,(3) мкс
или же 12 кГц
. Далее, складывая его, можно сформировать искомый сигнал. Для этого сделаем таймер, который будет срабатывать 12 тысяч раз в секунду.void setupTimer1() {
noInterrupts();
...
// 12003.000750187546 Hz (16000000/((1332+1)*1))
OCR1A = 1332;
// CTC
TCCR1B |= (1 << WGM12);
// Prescaler 1
TCCR1B |= (1 << CS10);
// Output Compare Match A Interrupt Enable
TIMSK1 |= (1 << OCIE1A);
interrupts();
}
Таким образом, 240 раз дёрнули таймер и получили посылку. Осталось её сформировать.
Помните мой прошлый обработчик прерывания, я ещё написал, что кто поймёт, что там происходит, получит конфетку. Никто не понял, не расстраивайтесь, я тоже спустя пару недель ничего не понял.
Главное, что в прошлый раз, никто не заругал меня за то, что я делил в критичном для времени коде! В результате обработчик взял и написал всё заново, хотя, признаюсь честно, гениальная простота нелегко далась.
ISR(TIMER1_COMPA_vect) {
static uint8_t frame_counter = 0;
static uint8_t cur_pos_num = 0;
if (frame_counter < 20) {
if (frame_counter & 1) {
if (numbers[cur_pos_num] == (9 - (frame_counter >> 1))) {
set_nego_sig();
} else {
set_zero_sig();
}
} else {
set_posi_sig();
}
}
if (frame_counter++ == 39) {
frame_counter = 0;
if (cur_pos_num < 5) {
cur_pos_num++;
} else {
set_nego_sig();
cur_pos_num = 0;
}
} else {
if (frame_counter > 20) {
set_zero_sig();
}
}
}
Код исполняется максимально быстро, как только может. Массив чисел
volatile uint8_t numbers[6]
я рассчитываю и загружаю отдельно, в некритичном месте, в функции load_time ()
.Ещё очень важное замечание поступило от Wan-Derer насчёт использования метода
digitalWrite()
, который на поверку оказался совершенно небыстрым. В результате функции вывода я переписал, и они стали выглядеть следующим образом (также инлайновые).void set_zero_sig() {
PORTD |= (1 << POS_SIG) | (1 << NEG_SIG);
}
void set_posi_sig() {
PORTD = PORTD &~ (1 << POS_SIG) | (1 << NEG_SIG);
}
void set_nego_sig() {
PORTD = PORTD &~ (1 << NEG_SIG) | (1 << POS_SIG);
}
Как видно, код стал очень простым, элегантным и легко читаемым. Хотя, признаюсь честно, так вылизать его стоило несколько ночей.
Результат превзошёл все мои ожидания, я получил идеальный сигнал, такой же по таймингам, как и оригинальный сигнал первичных часов. Внимание на дельту BX-AX:
Установка времени
Самой проблемной частью программы, и самой объёмной — это установка и вывод даты и времени. Долго-долго думал, как реализовать механизм установки. Визуально момент установки — это мигание в текущей позиции, на деле оказалось не очень удобно, но терпимо.
В результате решил погуглить, ведь всегда кто-то хорошее придумал для тебя. И нашёл весьма схожий проект часов, на точно таком же шилде. Из этого проекта я подчерпнул много интересного и позаимствовал некоторые решения.
Одна из интересных идей — это машина состояний, то есть программа у нас работает в различных состояниях, и в зависимости от того, в каком она состоянии, то и выполняет.
// Finite Machine States
enum FMS
{
RUN = 0,
SETHOUR,
SETMINUTE,
SETDAY,
SETMONTH,
SETYEAR
} ClockState;
То есть у нас есть состояние нормальной работы, установки часов, минут и т.д. Другая идея, это как осуществлять переключение машины состояний — очень просто:
// Finite Machine State Transitions Table.
// Defines the flow of the application modes from one to another.
enum FMS StateMachine[] =
{
/* RUN -> */ SETHOUR,
/* SETHOUR -> */ SETMINUTE,
/* SETMINUTE -> */ SETDAY,
/* SETDAY -> */ SETMONTH,
/* SETMONTH -> */ SETYEAR,
/* SETYEAR -> */ RUN
};
У меня есть специальный массив, где перещёлкиваеся состояние относительно предыдущего. Вот, например, если нажата кнопка выбора, состояние перещёлкивается, а если нажата кнопка влево, то возвращается в состояние запуск:
if (key == KEY_SELECT) {
ClockState = StateMachine[ClockState]; // switch mode
}
if (key == KEY_LEFT) {
ClockState = RUN; // switch mode
}
Ну а далее — дело техники.
Самое сложное — это установка дней месяцев, потому что какие-то месяцы не могут превышать 30 дней, а февраль вообще, в определённые годы (не високосные) должен быть не более 28, а в високосные не более 29. Вообще, я думал это всё повесить на совесть микросхемы RTC DS1307, но оказалось, что она прекрасно принимает на вход 31 февраля и работает с ним, поэтому пришлось решать такие проблемы своими силами.
С обычными месяцами всё более-менее просто, заводим массив констант дней в месяце и проверяем его:
const unsigned int month_days [] =
{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
А вот для определения високосного года пришлось завести отдельную функцию, её идею также позаимствовал из проекта. Итак, функция определения високосного года:
boolean test_leap_year(uint8_t year) {
uint16_t current_year = tmYearToCalendar(year);
if ((((current_year%400)==0) || ((current_year%4)==0)) && !((current_year%100)==0)) {
return true;
} else {
return false;
}
}
В результате код инкрементации дней выглядит следующим образом:
case SETDAY:
...
if ((m_time.Month != 2 && m_time.Day < month_days[m_time.Month])
||
(m_time.Month == 2 && m_time.Day < 28)
||
(m_time.Month == 2 && m_time.Day == 28 &&
test_leap_year(m_time.Year))) {
m_time.Day++;
}
break;
Аналогичный кусок кода идёт в конце, при записи уже в rtc, если вдруг был поставлен 31 день, а потом перещёлкнут месяц на февраль, то это решение не даст установить больше дней в месяце, чем может быть.
Могу честно сказать, високосные года вынесли мне мозг по полной программе, я на них потратил пару вечеров точно.
Из лайфхаков, как быстро поставить точное время, синхронизированное по NTP. В библиотеке часов есть пример «Set Time», он с помощью макросов получает точное время. Собираем его и заливаем, вуаля, у вас в часах ds1307 установлено точное время. Далее снова заливаем нашу программу.
Механика
О, это самая весёлая часть, которая отняла 40-50% от общего времени проекта. Многие самодельщики, да я думаю даже почти все, как правило, не делают корпус для своих поделок. И часто изделия так и существуют в виде плат и проводов, оправдывают они это тем, что мол и так всё работает.
Перед окончательной сборкой.
На самом деле, причина в другом, создание корпуса и механических частей, по трудозатратам и цене может превышать трудозатраты на создание платы и написание кода вместе взятых. Посмотрите на проект Flipper Zero, в каждой своей публикации они воюют с корпусом, катаются в Китай, прорабатывают дизайн и т.п. И это самая дорогая часть, потому что стоимость разработки кода несоизмеримы с накладными расходами переделать пресс-форму. Если ребята расскажут о распределении средств, то я уверен, что львиная доля средств идёт именно в механику (корпус и плату). И поэтому у самодельщиков вдохновения на код хватает, а на корпус уже нет.
Тем не менее я сторонник концепции, что устройство начинается с корпуса, о чём писал в своей статье. Поэтому делать буду хороший и качественный корпус.
Размер корпуса я подбирал исходя из размера платы дисплея и высоты бутерброда из трёх плат. Для него наиболее хорошо подошёл корпус размерами 135х75х50мм, из серого пластика.
Исходный корпус.
Из задач, которые передо мной стояли:
- Разметить верхнюю крышку, сделать отверстия под кнопки и экран.
- Рассчитать высоту проставок и толкателей.
- Начертить и распечатать толкатели и проставки.
- Расчертить боковые стенки под гнездо питания и разъём подключения вторичных часов.
Верхняя крышка
С ней было больше всего возни. Честно говоря, я был уверен, что есть готовый чертёж для LCD Keypad Shield, с которого я смогу срисовать себе крышку, но оказалось нет (что просто удивительно). Хотя были даже трёхмерные модели (которые тоже надо было как-то обрабатывать). В результате мне удалось на thingiverse найти готовую панель под лазерную резку. Из ценного там внутри был готовый dxf, который я и применил.
Самое неприятное в таких проектах, что кнопки не всегда совпадают. Самый простой способ проверки — это вырезать и приложить, что я и сделал.
Примерка чертежей.
Кнопки в целом совпали, небольшое отклонение компенсирует форма толкателей. То есть, кнопка не находится непосредственно над отверстием. В результате окончательный чертёж верхней крышки выглядит следующим образом:
Вооружаемся набором свёрл, лобзиком, напильниками и делаем верхнюю крышку.
К сожалению, я не догадался сделать фотографию финального результата, но он мало отличается от крышки в сборе.
Толкатели и проставки
После того, как крышка готова, вставил дисплей и замерил необходимое расстояние проставок и высоту толкателей.
Для изготовления проставок и толкателей пришлось освоить FreeCAD, оказалось очень годной вещью.
Для того чтобы определить оптимальную высоту пипочки, напечатал сразу несколько штук, с разной высотой. Так уж получилось, что в этом проекте я впервые столкнулся с 3d-моделированием, и в целом с 3d-печатью, поэтому сразу собрал кучу граблей: забитое сопло, ненастроенный толком слайсер и все прелести начинающего 3д-печатника.
Да, такое печальное качество.
В своё оправдание скажу, что так плохо принтер стал печатать в последнее время, и я предполагаю, что банально забилось сопло. Если честно, я бы пошёл на курсы тридепечати со своим принтером.
После примерки, толкатель встал нормально, но внешний вид, даже после механической обработки меня не устроил. Поэтому было принято решение сделать всё “по старинке” на токарном станке, заодно протестировать резцы по алюминию. Плюс такого подхода, что точность в сотку.
Точим толкатели.
Качество изготовления несравнимо с трёхмерной печатью (по крайне мере pla-пластиком).
Точёные толкатели, в сравнении с напечатанным.
Пару слов о технологической карте. Если говорить по сути, то всё можно сделать одним отрезным резцом, но по наружи всё же прошёлся проходным. Далее просто обтачивал все заготовки в размер, и потом единой операцией срезал.
Гнездо питания и разъём подключения вторичных часов
С клеммником для подключения часов пришлось помудрить, оказалось, что если взять артикулы данного клеммника (CH1-0401B, MPT-261, PT-2A, KLS1-WP-2P-03A), и поискать, то чертежи не соответствуют его фактическим размерам. Плюс, для ввода контактов внутрь корпуса там предполагают сделать квадратное окно. Посчитал такое решение нетехнологичным и большим расточительством.
Поэтому снял размеры самостоятельно, а для того чтобы ввести контакты в корпус, решил отверстие высверлить. Но, тут есть небольшой нюанс, если взять сверло соразмерно контакту, и просто просверлить посередине, то отверстие будет видно под корпусом клеммника. Кислые яблоки, зелёные щи, никто не читает мои вирши. Поэтому пришлось сделать отверстие большего диаметра, а колодку вписать по хорде. В результате получаем вот такой чертёж для клеммника и разъёма питания.
Печатаю на бумаге и примеряю, не ошибся ли я с размерами.
Клеммник и чертёж боковой стороны.
Корпус тоже оказался не прямоугольным в сечении, а трапецеидальным. Сверление в тонкостенном пластике следует делать ступенчатым сверлом. Вообще, если толщина материала либо равна, либо меньше диаметра отверстия, следует пользоваться ступенчатыми свёрлами или балеринками.
Сверлим все необходимые отверстия.
Вот теперь у нас всё готово к финальной сборке.
Сборка
Как я уже сказал, всю схемотехнику делал на макетной плате. Вообще, я чуточку пожалел, что не стал разводить плату под это дело, но тогда бы разработка затянулась ещё на месяц, а мне всё же хотелось уже получить готовое устройство.
Окончательная сборка.
На макетной плате слева расположились часы, батарейку я отпаял и расположил посередине, а справа расположился модуль Н-моста. Места в корпусе хватило бы на ещё одну такую макетную плату.
После сборки.
Мне кажется, что получилось вполне достойно для домашней поделки. Долго думал сделать винты в потай, а шурупы заменить на оцинкованные, но думаю это всё излишества. Тем более торчащие шляпки шурупов защищают хоть минимально экран контроллера.
Итог
Данный проект постарался сделать на пределе своих возможностей. Для него начал осваивать 3D-моделирование и 3D-печать (путь не идеально, но первые шаги сделаны), корпус также старался делать не на глаз, а всё чертил в программе. Код также стремился, как можно лучше причесать, и чтобы его могли использовать другие люди. Надеюсь, что этот проект тоже окажется кому-то полезен.
Хочу отметить ещё один любопытный факт, данный контроллер можно использовать не только со старыми, снятыми с производства часами «Воронеж», но и современными, типа ВЧЦ-100, ВЧЦ-160. На мою просьбу прислать осциллограммы первичных часов, откликнулся Валентин, который прислал мне на почту осциллограммы первичных часов ПЧЦ-ВС. И, просто потрясающе, что за столько лет сигнал практически не изменился!
Ну и куда же без видео-демонстрации работы.
Что хотелось бы ещё сделать: это заменить севшие лампы ИВ-26 тип 1 (тип 1 важен, другой не подойдёт), в количестве 20 штук. Быть может у кого завалялись такие лампы и лежат без дела, буду рад помощи.
Благодарности
Благодаря многим людям, которые делали замечания, помогали советом — получилась эта статья. Многих я упомянул в статье. Самая большая, отдельная благодарность sfrolov вообще за саму идею использования вторичных часов, за его первые посты, а также за помощь с осциллограммами. Выражаю самую большую признательность! Всем остальным, кто мне помогал, советовал, писал письма, присылал осциллограммы, выражаю большое человеческое спасибо! Всем кто советовал мне с 3д-печатью, за помощь с FreeCAD и вообще помощь в мотивации, громадное спасибо! Вы все крутые!
Полезные ссылки
- Моя первая статья, начало эпопеи: Создание контроллера вторичных часов «Воронеж».
- Репозиторий проекта на github.
- Чертежи деталей корпуса .
- LCD Keypad Shield for Arduino — Laser Panel.
- Проект, с которого почерпнул много идей.
- Любопытная статья про тестирование драйвера двигателя L9110.
Комментарии (20)
Gengenid
24.09.2021 18:06+4Не, ну начать проект - это большое дело.
А вот закончить - это вообще за гранью моего упорства. Можно только поздравить.
TIGER535
24.09.2021 22:11+3"меандр курильщика" (Явление Гиббса) ещё возникает в результате цифровой обработки сигнала в осциллографе, особенно на медленных развёртках, вы в целом писали об этом в своей статье, просто хочу акцентировать что это проявляется ещё и таким образом, амплитуда реального сигнала меньше чем нам показывает осциллограф
pt200
25.09.2021 11:06А не пробовали в качестве драйвера линии( вместо всяких L9110S и IR4427 ) "MAX232":?
1) рожден для этого
2) питание только 5В
3) встроенная защита выходов
...Виноват, там же еще 0 В есть.
dlinyj Автор
25.09.2021 12:27Я начал с этой идеи, но увы, ноль.
pt200
25.09.2021 13:59А нет схемы входных цепей часов( там самоподтяжки к "0" нет случаем)?
А то может задействовать 2 выхода RS232 через диодный смеситель( один тянет к +, другой к -, а 0 сам получается)dlinyj Автор
25.09.2021 14:15-1Зачем, если есть готовые микросхемы, специально для этих целей и достаточно дешёвые
pt200
25.09.2021 15:26Все таки биполярный и дифференциальный это немного разные физические интерфейсы, в первом случае у нас земля общая.
В результате дифференциальный выход на биполярный вход выглядит не очень: когда "земли" 2х приборов мечутся друг относительно друга в такт данным.
Wan-Derer
25.09.2021 15:26+1Как вы понимаете, число 20 очень плохо делится на числа кратные тройке
Зато 24 очень даже делится и на 3, и на 6, а вся приведённая математика пересчитывается в целые числа, без всяких периодов :)
dlinyj Автор
25.09.2021 17:09Все чудесно, но длительность посылки 20 мс, как согласно документации, так и снятым осциллограмм. 24 мс — это не кратная частота.
VT100
25.09.2021 16:36Решил всё же предать ГОСТовские схемы, и освоил-таки программу fritizing .....
Уже думал плюсануть, и тут...<Хнык>
Посмотрел на жменю горелых платок L9110S .....
Также — спасибо "доброму азиату", замкнувшему выход моста на C4. Который следует располагать у нагрузки (искрящего моторчика и т.п. индуктивности) и отделять от моста индуктивностью проводов (а иной раз — и дополнительным дросселем).
Или — уменьшить его раз в 50..100.dlinyj Автор
25.09.2021 17:10Уверяю, у меня есть схема, но она сильно хуже читаема.
VT100
25.09.2021 18:08Думаю — это всё равно лучше Фритцинга.
dlinyj Автор
25.09.2021 18:12Цель какая? Моя цель, чтобы те кому было нужно могли повторить. Если цель, понты, то можно привести схему с кучей соединений, и она станет не читаема.
Gengenid
26.09.2021 11:59+1Я раньше тоже к подобным схемам относился с некоторым снобизмом.
Но надо признать, это реально понятная монтажная схема.
И если выложить только привычную принципиальную схему, то чтобы собрать устройство, придется самому, неважно где, в голове или на экране или листе бумаги, составить подобную монтажную схему.
naething
25.09.2021 20:38У вас, кажется, в обработчике
TIMER1_COMPA_vect
в случаеframe_counter=20
ничего не происходит. Но я, собственно, не за этим.Для реализации подобных конечных автоматов можно из любопытства попытаться применить идею из https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html . С помощью нее можно писать код в императивном стиле, вместо явной обработки состояний:
#define BEGIN_COROUTINE static int32_t state = -1; switch(state) { case -1: #define YIELD do { state = __LINE__; return; case __LINE__: ; } while(0); #define END_COROUTINE } ISR(TIMER1_COMPA_vect) { static uint8_t cur_pos_num; static int8_t i; BEGIN_COROUTINE for (;;) { for (cur_pos_num = 0; cur_pos_num < 6; ++cur_pos_num) { for (i = 9; i >= 0; --i) { set_posi_sig(); YIELD; if (i == numbers[cur_pos_num]) { set_nego_sig(); } else { set_zero_sig(); } YIELD; } for (i = 0; i < 20; ++i) { set_zero_sig(); YIELD; } } } END_COROUTINE }
Если под номер состояния выделять большой int расточительно, то можно каждый раз явно передавать в YIELD какое-то уникальное число:
#define YIELD(s) do { state = s; return; case s: ; } while(0);
Практическая целесообразность под большим вопросом, но по-моему довольно интересный трюк.
dlinyj Автор
26.09.2021 17:33Честно признаюсь, силился понять что делает код, но понял что что-то совсем не то, что от него ожидают. Как минимум наличие:
for (;;) { ... }
В прерывании является бредом.
NickSin
Как хорошо, что по итогу весь пазл сложился. Успехов в поиске новых лам)
dlinyj Автор
Да, я сам очень рад. Спасибо большое за пожелание!