Проблемы с покупкой микроконтроллеров в России, плюс моя любовь к минимализму и желание заказчика сделать стоимость изделия минимальной, привели к разработке показанного на блок схеме устройства.
Спустя короткий промежуток времени, была поставлена задача — малой кровью сделать управление по линии связи устройством (назовем его «Блок управлением светом») на микроконтроллере PIC10F322, где большая часть из 512 программных слов уже было занято управлением светодиодами. Причем у PIC10F322 свободным для связи оставался только один вывод, который может работать только в режиме входа.
Сложность еще заключалась в том, что у микроконтроллера в блоке управления единственный UART был занят связью с панелью управления, поэтому замена в блоке управления светом микроконтроллера на более мощный с аппаратным UART, требовала так же замены микроконтроллера на более мощный с двумя UART в блоке управления. Захотелось найти более простое решение, не требующее замены микроконтроллеров, тем более заказчик утверждал, что дальнейшее увеличение функционала не предвидится.
Так как PIC10F322 управлял светодиодами через три сдвиговых регистра 74HC595, то каждые 320мкс он выдавал три пачки по восемь импульсов тактового сигнала и появилась идея организовать удаленный SPI интерфейс через микросхемы драйверов RS485.
PIC10F322 работает в режиме мастера, выдавая тактовый сигнал в линию, а базовый блок работает как подчиненный передатчик, выдавая обратно битовый поток команды. Так как между блоком управления и блоком света нет синхронизации, то PIC10F322 должен как-то понять где начало байта. Контроль ошибок делается за счет избыточности, если полученные три байта одинаковые, то значит ошибки нет. Так как битовый поток идет постоянно, то можно смело отбрасывать ошибочные кадры.
Стоит заметить, что блок управления управляет двухтактным преобразователем напряжения и ШИМ формирователем усилителя класса D, формируя звуковые сигналы оповещения, а также аналого-цифровое преобразование и простейшую обработку сигнала с микрофона. Поэтому времени у него просто в обрез и работать на прерываниях по SPI, где промежуток между байтами в кадре 10мкс не представляется возможным.
Хотелось бы найти решение, в котором записал байт в буфер и забыл. Так как синхронизация между блоками отсутствует, то базовый блок может начать передачу в произвольный момент времени. Вот так я пришел к тому, что нужны коды, которые при сдвиге не пересекаются с другими кодами.
Идея в следующем — если байт 0x01 циклически сдвигать по кругу, то его никогда не перепутаешь с так же циклически сдвинутым байтом 0x03 или 0x05 или 0x07.
Стало интересно, а сколько всего таких кодов в восьми битах? Оказалось таких кодов - 34, причем 16 из них идут подряд (0x80-0x8F), поэтому декодировать их очень легко.
В связи с тем, что мне нужно всего лишь 16 команд, программа получилась очень простая. Формирую тактовый сигнал, по заднему фронту считываю состояние входа и записываю в регистр. Полученный байт сохраняю в буфер. После заполнения буфера сравниваю значения элементов буфера, если одинаковые, то проверяю на попадание в диапазон 0x80 – 0x8F, если не попали, то сдвигаю два соседних байта в буфере и снова сравниваю, и так до семи раз. В случае любой ошибки пакет отбрасывается. Затем просто обнуляю старший бит и команда получена.
Полный список кодов которые, при сдвиге не принимают значение другого кода:
от 0x80 до 0x8F, 0x92, 0x93, 0x95, 0x96, 0x97, 0x99, 0x9A, 0x9B, 0x9D, 0x9E, 0x9F, 0xAA, 0xAB, 0xAD, 0xAF, 0xB7, 0xBB, 0xBF.
На КДПВ одинаковым цветом отмечены варианты сдвинутого числа, числа в рамочке, те которые я выбрал как «родителей» последовательности вариантов.
Вполне возможно, что такое давным давно придумали еще во времена Чарлза Бэббиджа, но беглый поиск по «самосинхронизирующиеся коды» интересующего меня результата не дал.
Комментарии (31)
Kazzman
06.01.2023 20:44Я такие коды точно изучал в университете. И самосинхрон и самоисправление и достаточность. Жаль не пригодилось только, и забылось. Но математическое обоснование модемной скорости 33.6 помню всех поразило. Когда сплошная математика, причем чисто умозрительная, бумажная, а тут вдруг раз и пример из реальной жизни....
Solgo Автор
06.01.2023 20:54Я тоже пытался вспоминать, но во-первых тридцать лет прошло с момента окончания политеха, во вторых специальность "Радиоэлектроника". Кроме кодов типа Манчестер и кодов Хэмминга вспомнить ничего не смог.
An_private
06.01.2023 23:58В общем-то, предложенное - это действительно некая разновидность канального кодирования, но только уж очень специфичная разновидность - с огромной избыточностью и отсутствием полноценной самосинхронизации. Не припомню ничего подобного в институте :) А так да - канальных кодов куча, начиная от простейшего манчестера и заканчивая довольно сложными типа 8/14 (EFM) и т.д.
Solgo Автор
07.01.2023 00:20Вы так и не пояснили почему отсутствует полноценная самосинхронизация. Устройство управления блоком света можно реализовать на одном 8 битном сдвиговом регистре с параллельными входами и все будет работать без микроконтроллера, в какой момент не включи такой пульт. А на счет огромной избыточности, вы выше предлагали еще большую избыточность 11бит на сообщение и вас это не смущало. К тому же меньше 8 бит передавать не получиться так как регистр передатчика 8 битный.
An_private
07.01.2023 00:50почему отсутствует полноценная самосинхронизация
Пояснил. Потому-что полноценный самосинхронизирующийся код позволяет однозначно определить положение в потоке. А в вашем случае при передаче 0x88 приёмник может встать как в правильное положение, так и со сдвигом на 4 бита.
А на счет огромной избыточности
Под избыточностью канального кодирования понимается - сколько бит необходимо передать для правильного приёма единичной посылки. В вашем случае единичная посылка - это 4 бита. Для того, чтобы её правильно принять вам надо передать 3 байта.То есть соотношение именно такое - для передачи 4 бит надо отправить 24.
При предложенном же мною варианте достаточно один раз отправить 5 бит синхропакета и далее достаточно отправлять 5 бит на каждые 4 входных. Ну, желательно время от времени повторять синхропакет. И даже в худшем варианте при отправке синхропакета на каждый пакет данных соотношение - 10 бит в канале на 4 бита данных (ноль после данных ставить совсем не нужно, это я ошибся).
Solgo Автор
07.01.2023 01:04При приеме со сдвигом на любое количество бит команда декодируется корректно.
На счет избыточности, во многих ситуациях , в том числе и в данной конструкции, время потраченное на передачу команды на много важнее. Как я уже сказал передатчик 8 битный, команда записывается в буфер только в момент смены режима и до следующей смены режима микроконтроллер со SPI буфером никаких действий не производит. Поэтому о какой избыточности вы говорите я не понимаю, наоборот присутсвует красивый минимализм.
Идельных решений подходящих для всех случаев жизни не существует, чтобы принять правильное решение надо исходить из ограничений накладываемых конструкцией, и только потом, если такая возможность осталась, из любви к исскуству.
shovdmi
07.01.2023 03:27+1так и со сдвигом на 4 бита.
У автора любой из возможных байт полученных сдвигом интерпретируется как одна команда
т. е. при чтении 0х88 или 0х11 или 0х22 или 0х44 - можно определить, что в регистре циклически сдвигается код команды х11
NickDoom
07.01.2023 01:37-1А если использовать 31 код (простое число), можно воспользоваться (не на микроконтроллере, конечно!) этой предельно лайтовой реализацией Рида-Соломона с говорящим названием, намекающим на происхождение:
Solgo Автор
07.01.2023 01:47Насколько я понимаю, коды Рида-Соломона используются для обнаружения и коррекции ошибок за счет избыточности. Но для того чтобы их использовать данные уже должны быть корректно приняты в виде кадра, после этого надо потратить заметное по меркам 8 битного микроконтроллера время чтобы обработать этот кадр по соответствующему алгоритму.
Это хорошо для большого количества данных в подверженной ошибкам среде, но задачу синхронизации и выделение кадра данных из битового потока они не решают, эта задача перекладывается на железо ближе к физическому протоколу обмена.
NickDoom
07.01.2023 02:16Именно так, это для тех случаев, когда кадр не коверкается до потери осязаемости границ, но пара-тройка символов в нём по жизни покорёженные. То есть уже после этого селектора, возможно, даже настроенного допускать при синхронизации несоответствие пары-тройки бит где-то внутри тела.
Но это явно уже не для таких микро-задач, как я сразу и оговорился :) Даже микро-Рид-Соломон уже очень жирный :)
gleb_l
07.01.2023 04:36А к любой комбинации последовательности разных кодов набор устойчив? Что будет, скажем, если приёмник получит произвольную часть битов команды X, а оставшуюся - Y? Ни одна такая комбинация не может быть воспринята, как валидная команда из множества?
Solgo Автор
07.01.2023 08:52+1В моей реализации все три принятых подряд байта должны быть одинаковыми, если это не так, смена команды, помеха - пакет отбрасывается.
gleb_l
07.01.2023 11:29+1На самом деле, я не понимаю, почему Вас ругают - на мой взгляд, задача решена минималистично-красиво - особенно в условиях, когда критичны ресурсы передатчика (обычно бывает наоборот). Вот бы ещё найти способ красиво делать индексный джамп по коду команды прямо из ее сырого представления (чтобы не крутить цикл 7 раз)..
Solgo Автор
07.01.2023 11:50В приемнике с процессорным временем проблем нет, поэтому я не стал делать поиск кода по индексу. Еще одна причина в том, что из доступных 512 программных слов использовано 433, т.е. в лоб табличка просто не влезет.
NickDoom
07.01.2023 18:05+1Ну я точно не ругаю — у меня просто сразу смежная генерация идей заработала, что ещё можно подкинуть в копилку «на будущее», вдруг на производные проекты пригодится. А это решение действительно идеальное.
Подозреваю, что с остальными примерно то же самое — сразу генераторы идей заработали :)
An_private
Как то сложно. Если надо передавать всего 16 команд, то куда проще передать пакет:
5 единиц --> 1 ноль -> код команды (4 бита) -> 1 ноль
Всё. В коде команды 5 единиц быть не может, поэтому 5 единиц однозначно будет синхропакетом
Solgo Автор
В вашем варианте сообщение получается 11 бит. Буфер в SPI PIC16F18346 размером 8 бит. В предложенном вами варианте я буду вынужден постоянно записывать в буфер SPI. В моем варианте записал и забыл (SDI соединен с SDO в SPI SLAVE) и команда сама отправляется с каждой пачкой тактовых импульсов.
An_private
А зачем постоянно записывать что-то в буфер? Или это постановка задачи такая - команда должна передаваться непрерывно? Для разовой передачи команды достаточно послать два байта - 0x1F и второй с командой.
То есть ваша идея понятна - сколько можно сформировать уникальных последовательностей с периодом 8 бит. Ок, оказалось, что таковых 34 (что-то у меня в голове крутится, что что-то подобное я где-то читал). Но вот в данном конкретном случае сложность реализации приёмной стороны, как мне кажется, довольно существенна и заметно нивелирует простоту реализации передающей стороны.
Solgo Автор
Как вы убедитесь что приемник принял сигнал без ошибки? После отправки какого количества команд вы сможете достоверно сказать, ну да, сейчас точно принял. Точно, точно..
An_private
Я не понимаю источника ошибок в вашем понимании. Если я принял 5 единиц подряд, то через 4 бита придёт команда. Всё. Какие тут еще ошибки? Если очень хочется то в этих двух байтах можно даже что-то типа контроля чётности организовать. Вот только зачем?
Кстати, в вашей реализации вместо 0x80-0x8F с тем же успехом можно использовать 0x10-0x1F - я правильно понимаю?
Solgo Автор
Любая линия связи подвержена воздействию помех. Поэтому ошибки точно будут, а их количество будет зависеть от электромагнитной обстановки вокруг.
An_private
Угу. Понятно. Понимаете, дело в том, что ваше решение не является в полном смысле этого слова самосинхронизирующимся кодом. Так как такие коды должны иметь возможность однозначного декодирования позиции в потоке. В вашем же случае при передаче кода 0x88 декодер может принять его как с правильным позиционированием, так и принять синхрослово (в вашем случае это 0x8 в верхнем ниббле) за полезные данные.
Да, в вашей конкретной задаче это неважно - ну вот задача такая - очень уж специфичная.
Solgo Автор
У меня нет синхрослова как такового, у меня все что передается это полезные данные.
An_private
Это не совсем так - в вашей реализации при пересылке каждого ниббла полезной нагрузки на него навешивается ниббл 0x8, который в данном случае и является синхрословом в его классическом понимании.
Solgo Автор
Это в моей реализации, если вы заходите использовать все 34 кода, ваша концепция разделения на нибблы уже не подойдет.
Solgo Автор
"Кстати, в вашей реализации вместо 0x80-0x8F с тем же успехом можно использовать 0x10-0x1F - я правильно понимаю? "
Да вы правы, эта последовательность тоже обладает данными свойствами.
Solgo Автор
Это вся реализация на приемной стороне, включая отправку на сдвиговые регистры, и декодирование команды. Как видите она не очень сложная.
Hisoka
Неплохо, но если пакетами передачу делать, то поделить пакет на кадры, к примеру, кадр 8 бит, начале каждого кадра 1 бит всегда установлен - т.е. оповещает что начался новый кадр. Тогда синхра это получить как минимум 8 нулей(нет кадра).
Если знать что вещатель каждые 10мс отправляет такой пакет, а время передачи такого пакета 5мс, то минимальное время чтобы принять каждый раз хоть 1 пакет - 10 + 5мс
Solgo Автор
Ваше предложение усложнит обработку на передающей и приемной стороне и при этом не принесет никаких улучшений. Изначально я делал пакет из трёх байт, 0xFF, 0x00, "байт с данными", но затем перешёл к описанном в статье варианту так как это сильно упростило обработку пакета с обоих сторон.
Толчком для переделки послужило то, что время между задним и передним фронтом тактовых импульсов чуть меньше 2мкс. И даже если микроконтроллер в базовом блоке ничего не делает, а только ждёт прерывания от SPI, то он все равно не успевает. И SPI входит в коллизию, так как передача байта уже началась, судя по переднему фронту тактового импульса, а тут происходит запись в буфер передатчика.
Hisoka
Ну это для передачи нормального числа данных, в общем случае. С ограниченным набором хоть кодировать по пропускам/заполненности бит можно. 11 - одно, 111 второе, 1111 третье. Или лучше 10 - одно, 100 - второе, 1000 - третье, тогда 11(конец)
Solgo Автор
Я не понимаю что такое "в общем случае". В разработке всегда решается конкретная задача исходя из ТЗ. Если будет задача передавать большее количество данных или поток данных (например аудио или видео поток) , то естественно будет использоваться другой протокол и другое железо. Можно придумать миллион вариантов синхросигналов и контроля целостности данных, но в данном конкретном случае описанном в статье, они будут хуже, так как не принесут улучшений, а только усложнят обработку.