Проблемы с покупкой микроконтроллеров в России, плюс моя любовь к минимализму и желание заказчика сделать стоимость изделия минимальной, привели к разработке показанного на блок схеме устройства.

Спустя короткий промежуток времени, была поставлена задача — малой кровью сделать управление по линии связи устройством (назовем его «Блок управлением светом») на микроконтроллере 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)


  1. An_private
    06.01.2023 17:10
    +3

    Как то сложно. Если надо передавать всего 16 команд, то куда проще передать пакет:

    5 единиц --> 1 ноль -> код команды (4 бита) -> 1 ноль

    Всё. В коде команды 5 единиц быть не может, поэтому 5 единиц однозначно будет синхропакетом


    1. Solgo Автор
      06.01.2023 17:15
      +3

      В вашем варианте сообщение получается 11 бит. Буфер в SPI PIC16F18346 размером 8 бит. В предложенном вами варианте я буду вынужден постоянно записывать в буфер SPI. В моем варианте записал и забыл (SDI соединен с SDO в SPI SLAVE) и команда сама отправляется с каждой пачкой тактовых импульсов.


      1. An_private
        06.01.2023 17:27
        +1

        А зачем постоянно записывать что-то в буфер? Или это постановка задачи такая - команда должна передаваться непрерывно? Для разовой передачи команды достаточно послать два байта - 0x1F и второй с командой.

        То есть ваша идея понятна - сколько можно сформировать уникальных последовательностей с периодом 8 бит. Ок, оказалось, что таковых 34 (что-то у меня в голове крутится, что что-то подобное я где-то читал). Но вот в данном конкретном случае сложность реализации приёмной стороны, как мне кажется, довольно существенна и заметно нивелирует простоту реализации передающей стороны.


        1. Solgo Автор
          06.01.2023 17:33
          +2

          Как вы убедитесь что приемник принял сигнал без ошибки? После отправки какого количества команд вы сможете достоверно сказать, ну да, сейчас точно принял. Точно, точно..


          1. An_private
            06.01.2023 17:44

            Я не понимаю источника ошибок в вашем понимании. Если я принял 5 единиц подряд, то через 4 бита придёт команда. Всё. Какие тут еще ошибки? Если очень хочется то в этих двух байтах можно даже что-то типа контроля чётности организовать. Вот только зачем?

            Кстати, в вашей реализации вместо 0x80-0x8F с тем же успехом можно использовать 0x10-0x1F - я правильно понимаю?


            1. Solgo Автор
              06.01.2023 17:47
              +1

              Любая линия связи подвержена воздействию помех. Поэтому ошибки точно будут, а их количество будет зависеть от электромагнитной обстановки вокруг.


              1. An_private
                06.01.2023 18:15
                +3

                Угу. Понятно. Понимаете, дело в том, что ваше решение не является в полном смысле этого слова самосинхронизирующимся кодом. Так как такие коды должны иметь возможность однозначного декодирования позиции в потоке. В вашем же случае при передаче кода 0x88 декодер может принять его как с правильным позиционированием, так и принять синхрослово (в вашем случае это 0x8 в верхнем ниббле) за полезные данные.

                Да, в вашей конкретной задаче это неважно - ну вот задача такая - очень уж специфичная.


                1. Solgo Автор
                  06.01.2023 18:20
                  +3

                  У меня нет синхрослова как такового, у меня все что передается это полезные данные.


                  1. An_private
                    06.01.2023 18:24
                    +1

                    Это не совсем так - в вашей реализации при пересылке каждого ниббла полезной нагрузки на него навешивается ниббл 0x8, который в данном случае и является синхрословом в его классическом понимании.


                    1. Solgo Автор
                      06.01.2023 18:30
                      +1

                      Это в моей реализации, если вы заходите использовать все 34 кода, ваша концепция разделения на нибблы уже не подойдет.


            1. Solgo Автор
              06.01.2023 17:53
              +1

              "Кстати, в вашей реализации вместо 0x80-0x8F с тем же успехом можно использовать 0x10-0x1F - я правильно понимаю? "
              Да вы правы, эта последовательность тоже обладает данными свойствами.


        1. Solgo Автор
          06.01.2023 17:40
          +1

          ;*********************************************************************	
          ;Выхлоп в сдвиговые регистры	
          SEND_DATA:
          	movlw	SEND_DATA_2
          	movwf	FSR
          	movlw	SEND_MASK_INI
          	movwf	MASK_SEND_REG
          	movlw	NUM_SEND_BYTES
          	movwf	CNT_SEND_BYTES
          	movlw	NUM_SEND_BITS
          	movwf	CNT_SEND_BITS
          SEND_DATA_CIRCLE:	
          	rlf	RX_DATA_NEW,f
          	movf	INDF,w
          	andwf	MASK_SEND_REG,w
          	btfss	STATUS,STATUS_Z_POSITION
          	goto	SEND_HIGH
          	bcf	LATA,SER
          	goto	SEND_SET_CLK
          SEND_HIGH:
          	bsf	LATA,SER
          SEND_SET_CLK:
          	bsf	LATA,SCK
          	bcf	STATUS,STATUS_C_POSITION
          	rrf	MASK_SEND_REG,f
          	nop
          	nop
          	nop
          	nop	;Нопы чтобы удлинить фронт так как на входе стоит резистор 4.7К
          	nop	;Для защиты драйвера при программировании микроконтроллера.
          	nop
          	nop
          	nop
          	bcf	RX_DATA_NEW,RX_BIT
          	btfsc	PORTA,RX
          	bsf	RX_DATA_NEW,RX_BIT
          	bcf	LATA,SCK
          	decfsz	CNT_SEND_BITS,f
          	goto	SEND_DATA_CIRCLE
          ;***Сохраняем принятые данные в буфер****
          	movlw	RX_DATA_0 - SEND_DATA_0
          	addwf	FSR,f
          	movf	RX_DATA_NEW,w
          	movwf	INDF
          	movlw	RX_DATA_0 - SEND_DATA_0
          	subwf	FSR,f
          ;**********************************	
          	movlw	NUM_SEND_BITS
          	movwf	CNT_SEND_BITS
          	movlw	SEND_MASK_INI
          	movwf	MASK_SEND_REG
          	decf	FSR,f
          	decfsz	CNT_SEND_BYTES,f
          	goto	SEND_DATA_CIRCLE
          	bsf	LATA,RCK
          	bcf	LATA,RCK
          ;Конец функции отправки данных в сдвиговые регистры	
          ;Передача закончена декодируем принятые данные
          ;Проверяем на одинаковость
          	movf	RX_DATA_0,w
          	xorwf	RX_DATA_1,w
          	btfss	STATUS,STATUS_Z_POSITION
          	goto	CLEAR_WDT
          	movf	RX_DATA_0,w
          	xorwf	RX_DATA_2,w
          	btfss	STATUS,STATUS_Z_POSITION
          	goto	CLEAR_WDT
          ;Проверяем на диапазон 0x80-0x8F
          	movlw	NUM_SEND_BITS-1
          	movwf	CNT_SEND_BITS
          RX_DATA_TEST_RANGE:	
          	movlw	RX_CODE_BEGIN
          	subwf	RX_DATA_1,w
          	btfss	STATUS,STATUS_C_POSITION
          	goto	RX_CODE_TEST_RLF
          	movf	RX_DATA_1,w
          	sublw	RX_CODE_END
          	btfss	STATUS,STATUS_C_POSITION
          	goto	RX_CODE_TEST_RLF
          ;Данные в диапазоне 0x80-0x8F декодируем
          	movlw	MASK_RX_DATA
          	andwf	RX_DATA_1,f
          	movf	RX_DATA_1,w
          	xorwf	MODE_REG,w
          	btfsc	STATUS,STATUS_Z_POSITION
          	goto	CLEAR_WDT//Данные уже в регистре режима
          	movf	RX_DATA_1,w
          	movf	RX_DATA_1,w
          	xorwf	MODE_REG,w
          	andlw	MASK_MODE_DATA
          	btfss	STATUS,STATUS_Z_POSITION
          	bsf	SYSTEM_FLAGS,MODE_INI
          	movf	RX_DATA_1,w
          	movwf	MODE_REG
          	goto	CLEAR_WDT//Данные уже в регистре режима
          ;Данные не в диапазоне 0x80-0x8F сдвигаем влево	
          RX_CODE_TEST_RLF:
          	movf	CNT_SEND_BITS,w
          	btfsc	STATUS,STATUS_Z_POSITION
          	goto	CLEAR_WDT//данные приняты с ошибкой
          	decf	CNT_SEND_BITS,f
          	rlf	RX_DATA_0,f
          	rlf	RX_DATA_1,f
          	goto	RX_DATA_TEST_RANGE
          CLEAR_WDT:	
          	clrwdt
          	goto	WAIT_TMR2

          Это вся реализация на приемной стороне, включая отправку на сдвиговые регистры, и декодирование команды. Как видите она не очень сложная.


    1. Hisoka
      07.01.2023 07:37

      Неплохо, но если пакетами передачу делать, то поделить пакет на кадры, к примеру, кадр 8 бит, начале каждого кадра 1 бит всегда установлен - т.е. оповещает что начался новый кадр. Тогда синхра это получить как минимум 8 нулей(нет кадра).

      Если знать что вещатель каждые 10мс отправляет такой пакет, а время передачи такого пакета 5мс, то минимальное время чтобы принять каждый раз хоть 1 пакет - 10 + 5мс


      1. Solgo Автор
        07.01.2023 09:17

        Ваше предложение усложнит обработку на передающей и приемной стороне и при этом не принесет никаких улучшений. Изначально я делал пакет из трёх байт, 0xFF, 0x00, "байт с данными", но затем перешёл к описанном в статье варианту так как это сильно упростило обработку пакета с обоих сторон.

        Толчком для переделки послужило то, что время между задним и передним фронтом тактовых импульсов чуть меньше 2мкс. И даже если микроконтроллер в базовом блоке ничего не делает, а только ждёт прерывания от SPI, то он все равно не успевает. И SPI входит в коллизию, так как передача байта уже началась, судя по переднему фронту тактового импульса, а тут происходит запись в буфер передатчика.


        1. Hisoka
          07.01.2023 09:25

          Ну это для передачи нормального числа данных, в общем случае. С ограниченным набором хоть кодировать по пропускам/заполненности бит можно. 11 - одно, 111 второе, 1111 третье. Или лучше 10 - одно, 100 - второе, 1000 - третье, тогда 11(конец)


          1. Solgo Автор
            07.01.2023 09:43

            Я не понимаю что такое "в общем случае". В разработке всегда решается конкретная задача исходя из ТЗ. Если будет задача передавать большее количество данных или поток данных (например аудио или видео поток) , то естественно будет использоваться другой протокол и другое железо. Можно придумать миллион вариантов синхросигналов и контроля целостности данных, но в данном конкретном случае описанном в статье, они будут хуже, так как не принесут улучшений, а только усложнят обработку.


  1. Kazzman
    06.01.2023 20:44

    Я такие коды точно изучал в университете. И самосинхрон и самоисправление и достаточность. Жаль не пригодилось только, и забылось. Но математическое обоснование модемной скорости 33.6 помню всех поразило. Когда сплошная математика, причем чисто умозрительная, бумажная, а тут вдруг раз и пример из реальной жизни....


    1. Solgo Автор
      06.01.2023 20:54

      Я тоже пытался вспоминать, но во-первых тридцать лет прошло с момента окончания политеха, во вторых специальность "Радиоэлектроника". Кроме кодов типа Манчестер и кодов Хэмминга вспомнить ничего не смог.


      1. An_private
        06.01.2023 23:58

        В общем-то, предложенное - это действительно некая разновидность канального кодирования, но только уж очень специфичная разновидность - с огромной избыточностью и отсутствием полноценной самосинхронизации. Не припомню ничего подобного в институте :) А так да - канальных кодов куча, начиная от простейшего манчестера и заканчивая довольно сложными типа 8/14 (EFM) и т.д.


        1. Solgo Автор
          07.01.2023 00:20

          Вы так и не пояснили почему отсутствует полноценная самосинхронизация. Устройство управления блоком света можно реализовать на одном 8 битном сдвиговом регистре с параллельными входами и все будет работать без микроконтроллера, в какой момент не включи такой пульт. А на счет огромной избыточности, вы выше предлагали еще большую избыточность 11бит на сообщение и вас это не смущало. К тому же меньше 8 бит передавать не получиться так как регистр передатчика 8 битный.


          1. An_private
            07.01.2023 00:50

            почему отсутствует полноценная самосинхронизация

            Пояснил. Потому-что полноценный самосинхронизирующийся код позволяет однозначно определить положение в потоке. А в вашем случае при передаче 0x88 приёмник может встать как в правильное положение, так и со сдвигом на 4 бита.

            А на счет огромной избыточности

            Под избыточностью канального кодирования понимается - сколько бит необходимо передать для правильного приёма единичной посылки. В вашем случае единичная посылка - это 4 бита. Для того, чтобы её правильно принять вам надо передать 3 байта.То есть соотношение именно такое - для передачи 4 бит надо отправить 24.

            При предложенном же мною варианте достаточно один раз отправить 5 бит синхропакета и далее достаточно отправлять 5 бит на каждые 4 входных. Ну, желательно время от времени повторять синхропакет. И даже в худшем варианте при отправке синхропакета на каждый пакет данных соотношение - 10 бит в канале на 4 бита данных (ноль после данных ставить совсем не нужно, это я ошибся).


            1. Solgo Автор
              07.01.2023 01:04

              При приеме со сдвигом на любое количество бит команда декодируется корректно.

              На счет избыточности, во многих ситуациях , в том числе и в данной конструкции, время потраченное на передачу команды на много важнее. Как я уже сказал передатчик 8 битный, команда записывается в буфер только в момент смены режима и до следующей смены режима микроконтроллер со SPI буфером никаких действий не производит. Поэтому о какой избыточности вы говорите я не понимаю, наоборот присутсвует красивый минимализм.

              Идельных решений подходящих для всех случаев жизни не существует, чтобы принять правильное решение надо исходить из ограничений накладываемых конструкцией, и только потом, если такая возможность осталась, из любви к исскуству.


            1. shovdmi
              07.01.2023 03:27
              +1

              так и со сдвигом на 4 бита.

              У автора любой из возможных байт полученных сдвигом интерпретируется как одна команда

              т. е. при чтении 0х88 или 0х11 или 0х22 или 0х44 - можно определить, что в регистре циклически сдвигается код команды х11


  1. NickDoom
    07.01.2023 01:37
    -1

    А если использовать 31 код (простое число), можно воспользоваться (не на микроконтроллере, конечно!) этой предельно лайтовой реализацией Рида-Соломона с говорящим названием, намекающим на происхождение:

    https://github.com/NickDoom-IDKFA/Reed-Solomon_Habrahabr


    1. Solgo Автор
      07.01.2023 01:47

      Насколько я понимаю, коды Рида-Соломона используются для обнаружения и коррекции ошибок за счет избыточности. Но для того чтобы их использовать данные уже должны быть корректно приняты в виде кадра, после этого надо потратить заметное по меркам 8 битного микроконтроллера время чтобы обработать этот кадр по соответствующему алгоритму.

      Это хорошо для большого количества данных в подверженной ошибкам среде, но задачу синхронизации и выделение кадра данных из битового потока они не решают, эта задача перекладывается на железо ближе к физическому протоколу обмена.


      1. NickDoom
        07.01.2023 02:16

        Именно так, это для тех случаев, когда кадр не коверкается до потери осязаемости границ, но пара-тройка символов в нём по жизни покорёженные. То есть уже после этого селектора, возможно, даже настроенного допускать при синхронизации несоответствие пары-тройки бит где-то внутри тела.

        Но это явно уже не для таких микро-задач, как я сразу и оговорился :) Даже микро-Рид-Соломон уже очень жирный :)


  1. gleb_l
    07.01.2023 04:36

    А к любой комбинации последовательности разных кодов набор устойчив? Что будет, скажем, если приёмник получит произвольную часть битов команды X, а оставшуюся - Y? Ни одна такая комбинация не может быть воспринята, как валидная команда из множества?


    1. Solgo Автор
      07.01.2023 08:52
      +1

      В моей реализации все три принятых подряд байта должны быть одинаковыми, если это не так, смена команды, помеха - пакет отбрасывается.


      1. gleb_l
        07.01.2023 11:29
        +1

        На самом деле, я не понимаю, почему Вас ругают - на мой взгляд, задача решена минималистично-красиво - особенно в условиях, когда критичны ресурсы передатчика (обычно бывает наоборот). Вот бы ещё найти способ красиво делать индексный джамп по коду команды прямо из ее сырого представления (чтобы не крутить цикл 7 раз)..


        1. Solgo Автор
          07.01.2023 11:50

          В приемнике с процессорным временем проблем нет, поэтому я не стал делать поиск кода по индексу. Еще одна причина в том, что из доступных 512 программных слов использовано 433, т.е. в лоб табличка просто не влезет.


        1. NickDoom
          07.01.2023 18:05
          +1

          Ну я точно не ругаю — у меня просто сразу смежная генерация идей заработала, что ещё можно подкинуть в копилку «на будущее», вдруг на производные проекты пригодится. А это решение действительно идеальное.

          Подозреваю, что с остальными примерно то же самое — сразу генераторы идей заработали :)