Приветствую вас, друзья!

Продолжаем знакомство с платой SX100. Рассмотрим структуру и некоторые любопытные особенности платы, узнаем, как улучшить ЭМС с помощью расширения спектра. Сделаем пару сотен UARTов и доработаем плату.

Вперёд к экспериментам!


В прошлый раз мы познакомились с платой SX100, рассмотрели способы определения распиновки микросхем в корпусах BGA. Один из вариантов – повесить по UART передатчику на выводы и слушать. Пришло время провести этот любопытный эксперимент!

Делаем сотни UARTов


Выводы в корпусах с матрицей шариков (BGA) называются по строке и столбцу, на пересечении которых находятся. Например, например, А5, W22, P7, AA15… Можно попробовать реализовать UART как кольцевой регистр сдвига, передающий нужную строку с именем вывода, разделённую на байты с добавлением старт- и стоп-битов.



Сделаем проще: будем передавать не строки (коды символов) с именами выводов, а числа. Это облегчит генерацию экземпляров UART передатчиков.

Числа сделаем от 0 до 255, чтобы уложиться в 8 бит и посылать 1 байт, а не 2.

Получается, что передатчиков у нас столько же, сколько щупальцев у тридцати двух осьминогов ☺.



Добавим 1 старт-бит, 1 стоп-бит. Поскольку регистр закольцованный, он будет передавать одну и ту же 10-битную посылку постоянно.

Делать скорость UART стандартной необязательно. Сделаем любую удобную, а потом просто впишем её в терминале:



Поделим частоту 25 МГц с генератора на 250 и получим 100 кГц. Этот сигнал будет тактировать наши UART передатчики. Для деления используем счётчик по модулю 125. Ещё 2 добавит инвертирование.

Код делителя частоты
module clk_div_250( 
input clk_in, 	
output reg clk_out
); 
 
//Инвертируем состояние выхода дважды за период. Получается частота ниже в 2 раза. Поэтому 250/2=125
parameter MODULE = 125;

reg [7:0]count = 1'b0; //счетчик

always @(posedge clk_in) 
begin	
    if(count==MODULE-1)	
    begin	
        count <= 0; //обнуляем счетчик		
        clk_out <= ~clk_out; //Инвертируем состояние выхода	
    end
    else 
       count <= count + 1'b1; //увеличиваем счетчик 
end 
endmodule

Код одного экземпляра UART передатчика
//Модуль для постоянной передачи по Юарт пакетов 
//вида 1 старт-бит =0, 8 бит данных, 1 стоп-бит =1
module cycled_uart_transmitter(	
    input clk, //Тактовый сигнал	
    output reg Tx //Информационный выход
);

// Данные. Максимум 255
parameter data = 8'b1010_1010;

// хранилище данных 10 бит.   
reg [9:0] uart_data;

reg [3:0]i= 1'b0; //счетчик битов = 0

always @ (posedge clk) //с каждым тактом
begin
	
    //Записываем начальное значение регистра	
    uart_data[0] <= 0;//1 старт-бит = 0,
    uart_data[8:1] <= data;//8 бит данных,
    uart_data[9] <= 1;//1 стоп-бит = 1
	
    Tx <= uart_data[i]; //отправили 1 бит данных
	
    i <= i + 1'b1; //перешли к следующему биту данных
	
    if(i==9) // если все 10 бит отправлены	
       begin
         i <= 0;  //обнулить счетчик битов
       end	
end

endmodule

Теперь нам нужно создать схему, в которой мы установим 1 делитель частоты на 250 и 256 экземпляров UART передатчиков. Если рисовать это на схеме, будет ооочень долго. Можно прописывать соединения на языке Verilog, описывая каждый модуль вручную. Это быстрее, но тоже долго.

Поэтому мы используем цикл и конструкцию «generate». Здесь нам будет проще из-за передачи чисел, а не строк. Мы просто передадим передатчикам в качестве параметра текущее значение счётчика цикла генерации. Получается вот такое небольшое описание. Красота!

Штампуем 256 передатчиков
//Модуль создания сотен Юартов. В данном случае будет 256.
module top_100s_of_UARTs(
    input CLK_IN,
    output CLK_100k,//Выведем сигнал, чтобы проверить осциллографом
    output [255:0]UART_OUT//Шина 256-ти выходов UART      
);

wire  clk_100k;//Выход частоты 100 кГц

clk_div_250 clk_div_250_0(//Счётчик-делитель на 250	
    .clk_in(CLK_IN),
    .clk_out(clk_100k)
);

//Далее создаём 256 экземпляров UART передатчика (cycled_uart_transmitter)
genvar n;//Счётчик для цикла
generate
for(n = 0; n < 256; n = n + 1 )
begin : cycled_uart_transmitter_generation
//имя экземпляра не имеет значения в дальнейшем
//Здесь переопределяем параметр каждого экземпляра Юарт знаком #
//Все юарты передают число n от 0 до 255.
//Каждый новый Юарт будет передавать своё число.
cycled_uart_transmitter #(n) cycled_uart_transmitter_insts
(		
  .clk(clk_100k),//На вход подаём 100 кГц	
  .Tx(UART_OUT[n]) //Выход UART
);
end
endgenerate

assign CLK_100k = clk_100k;

endmodule

Файлы проекта:


Синтезируем наш проект, выводим сигналы на разъёмы. Для быстроты выборочно выведем часть из 256 сигналов: с конца, с середины и начала шины UART_OUT. 



Компилируем. Ресурсов проект занимает очень мало. В основном, выводы.



Смотрим, что на CLK_100k:



Выборочно смотрим, что на разъёмах, куда мы вывели наши числа.
R9, число 253.





Число 0. Хорошо видны стоп-бит, старт-бит, 8 бит данных, 1 стоп-бит и 1 старт-бит следующего кадра.





Интересно, что число 85 = 0b0101_0101 выглядит как меандр.



Но терминал принимает верно.



А вот число 170 = 0b1010_1010 терминал принимает неправильно.





Мы передаём одну за одной посылки вида:

0 (старт-бит) | 8 бит данные | 1 (стоп-бит) | 0 (старт-бит) | 8 бит данные | 1 (стоп-бит) | … И они сливаются в меандр. Можно добавить в наш кадр по одному стоп-биту (лог. 1) или временную задержку в начале и конце.

При необходимости можно добавить передатчикам разные функции. Мы же будем двигаться дальше – нас ждёт ещё много интересного!

Вот она, разница между ПЛИС и микроконтроллером!

Структурная схема платы


Для удобства работы сделаем структурную схему. По мере поступления информации схема может дополняться. Сейчас она имеет такой вид:



В центре схемы ПЛИС. К ней подключено больше всего устройств.
Прошивка загружается в FPGA микроконтроллером Nuvoton NUC123LD4AN0 из flash-памяти 128 Мбит по SPI1 в пассивном последовательном (PS) режиме. Нувотон можно сбросить, и загружать конфигурацию ПЛИС по JTAG, что мы и делаем в процессе экспериментов.

Соответствующие выводы RGB HDMI приёмника и передатчика соединены между собой. Например, Hsync микросхемы ADV7612 соединён с Hsync ADV7511, R0…R7 – c R0…R7 и т д. Получается, принятый HDMI –> RGB сигнал можно подать на ПЛИС и HDMI передатчик одновременно. Другой вариант – отключить HDMI приёмник и подавать на передатчик сигнал с ПЛИС.

А ещё на плате необычное подключение между микроконтроллерами STM32 и Nuvoton NUC123LD4AN0: не напрямую по UART, а через HC4052.



Вывод выбора канала S1 подключен к земле (низкий уровень), а S0 управляется STM32.



К nY1 подключен UART STM32. Что подключено к nY0?

Можно подумать, что к микроконтроллеру Нувотон подключается какое-то ещё устройство по UART0. Но почему тогда не он переключает каналы с помощью S0, а STM32? Выходит другого устройства нет и STM32 просто отключает внука от своего UART1. Только зачем?

Если бы к UART1 STM32 было подключено ещё какое-то устройство, то при таком подключении HC4052 и выбора канала с Нувотоном, к UART1 STM32 оказались бы подключены сразу 2 устройства. Видимо, кроме Нувотона к UART1 STM32 ничего не подключено, а STM32 просто иногда хочет побыть наедине. Немного странное решение.

Выводы 86 (USART2_TX), 87 (USART2_RX) STM32 выведены на гребёнку, но ничего интересного там не происходит.

Схема тактирования и расширение спектра


Колебание 25 МГц генерируется микросхемкой с маркировкой X2 A0KGF. Найти точно такую же не удалось, но корпусом и распиновкой похоже на SiT2024B.

Сигнал 25 МГц (c выв 5 Х2) через резистор R180 = 33 Ом идёт к ПЛИС (на AA12) и на clock buffer/multiplier U41 (на вывод 1-in).



С вывода 6-out U41 колебание идёт через R108 = 33 Ом к ПЛИС (на АВ11).
Вывод 5-FS CDCS503 сидит на земле, поэтому умножения нет. Напрашивается вопрос: зачем тогда эта деталь? В названии есть слово «buffer», поэтому можно предположить, что выполняется какая-то изоляция источника синхросигнала от ПЛИС.

Выводы SSC_SEL0, SSC_SEL1 не соединены с землёй, значит, на них высокий уровень, так как есть внутренние подтяжки.



Значит есть расширение спектра +-2%.



Интересно, что на выходе генератора (левая часть рисунка) не меандр, а треугольный сигнал. Мне казалось, что на выходе буфера CDCS503 (правая часть рисунка) сигнал будет более красивым (может, для этого микросхему и поставили), но нет. Видно некоторое фазовое дрожание (jitter).



Дрожание в тактовый сигнал добавляется специально, чтобы расширить спектр. Но оно может быть неприемлемым для некоторых периферийных устройств, таких как АЦП, ЦАП и таймеры.
Расширение спектра (SSCG – spread spectrum clock generation) уменьшает пиковую энергию на основной частоте и на её гармониках, что помогает выполнить требования ЭМС. (AN4850 STM32 MCUs spread-spectrum clock generation principles, properties and implementation)
На картинке хорошо видно разницу.



Уровень излучения на частоте 120 МГц снизился примерно на 20 дБ или в 100 раз!



На ПЛИС идёт сигнал с генератора без расширения спектра и с расширением после CDCS503. Можно выбрать любой.

Что делать с 74HC245?


Особенность большинства подобных плат в том, что на них установлены микросхемы 74HC245, настроены они на работу в одном направлении (от ПЛИС к разъёмам) и питаются от 5 В, что много для ПЛИС и не позволяет просто изменить направление с помощью вывода DIR. Можно попробовать отпаять вывод Vcc микросхемы 74HC245, изолировать его от пада термоскотчем или соплеклеем и питать от нужного уровня напряжения. C выводом DIR поступить также.
Если нужно убрать 74HC245:

  1. Отпаять и заменить проволочками/кусочком шлейфа fpc с нужным шагом или платкой, как на картинке:



  2. Использовать гибкие печатные платы:





    Симпатично. Подробнее здесь.
  3. Встречал вариант замены микросхемы на двунаправленный вариант SN74CBT3245APW. Только смысла большого здесь не вижу: хоть у микросхемы вместо вывода direction (выбор направления) и not connected, но Vcc то всё равно сидит на 5В. Разве что изолировать его от 5 В и подавать нужное напряжение.

Для большей универсальности платы решил отпаять 74HC245 и заменить их перемычками:



Сначала я не очень хотел это делать, ведь мне казалось, что будет долго. Но потом решился, так как это даёт больше возможностей. Сперва я возился с каждой проволочкой и припаял так штук 10. Потом взял правильный пинцет, поменял жало у паяльника на «миниволну», немного приноровился и процесс просто полетел. Всё оказалось не сложнее, чем припаять какой-нибудь резистор 0805. Снизу пинцет «до», а сверху уже удобный для этого случая пинцет «после»:



А отпаивать микросхемы очень удобно сразу двумя паяльниками в две руки. В каждом установить жало «косой нож». Остаётся только отмыть флюс, проверить пайку и можно защищать соплеклеем. Или как-то ещё.



Кому-то нравится ремонтировать технику, узнавая при этом что-то новое и изучая, как оно устроено. Мне же нравится обратная разработка – ощутимо повышается мастерство.

Продолжение следует…



Возможно, захочется почитать и это:


Новости, обзоры продуктов и конкурсы от команды Timeweb.Cloud — в нашем Telegram-канале

Комментарии (10)


  1. gorod0k
    16.04.2024 04:57

    Удачная находка, девборда с 4 циклоном и кучей периферии и даже микроконтроллерами.

    Но мне кажется, это была не случайность, вы знали, что ищете)


    1. Kopcheniy Автор
      16.04.2024 04:57

      Без сюрпризов не обошлось.) Опыт получился весьма интересный и полезный, а процесс увлекательный.

      А ещё через плату познакомился с человеком, который её тоже покупал. Подружились.) И это, пожалуй, самое неожиданное, забавное и приятное в этой истории.


  1. alcotel
    16.04.2024 04:57

    А вот число 170 = 0b1010_1010 терминал принимает неправильно.

    А откуда уверенность, что именно осцилл принял правильно, а не терминал?)

    Осцилл и UART-приёмник просто не понимают, где именно старт- и стоп- биты, поэтому ставят их рандомно. Тот же паттерн 0.01010101.1 любой приёмник имеет право принять с циклическим сдвигом:
    001010101100101010110010101011 или
    001010101100101010110010101011 или
    001010101100101010110010101011 или
    001010101100101010110010101011, и это всё разные цифры. Короче:

    Давайте делать паузы в словах... (с)

    И не надо мутить воду:

    Можно попробовать реализовать UART как кольцевой регистр сдвига

    Tx <= uart_data[i]; //отправили 1 бит данных

    То, что в коде записано, это селектором называется. Или мультиплексором.

    Полноценны UARTовский регистр сдвига в этой задаче занял бы намного больше ресурсов ПЛИС. А так выходит, что каждому паттерну достаточно храниться в одной LUT. Метод весьма хорош.


    1. Kopcheniy Автор
      16.04.2024 04:57

      А откуда уверенность, что именно осцилл принял правильно, а не терминал?)

      Чётко знаю, на какой вывод какое число я назначил. В осциллографе можно сдвинуть момент синхронизации, тогда меняется и число.

      Осцилл и UART-приёмник просто не понимают, где именно старт- и стоп- биты

      Понятное дело. Можно пробовать паузы. Об этом написал.

      Идея пришла пришла именно про регистр сдвига. Такой вариант тоже есть.

      Да, про мультиплексор вы правы. Спасибо за замечание.

      Но если точнее, то у нас регистр с записанным числом+старт/стоп битами (uart_data,8+2 бит). К нему подключены информационные входы мультиплексора. К адресным входам мультиплексора подключается счётчик (i).

      Конечно, это не полноценный UART (как приёмопередатчик). Это лишь любопытный эксперимент.


      1. alcotel
        16.04.2024 04:57

        Я больше вот про что:
        Ну как-бы изначально цель работы-то была - выяснить распиновку. А с текущим кодом выходит, что группы из 2-4 ног выдают наружу одинаковые паттерны. Цель-то в итоге достигнута?

        //с каждым тактом

        //Записываем начальное значение регистра

        Других значений в uart_data не записывается. Сброса нет, начального значения сигнала uart_data хотя-бы для симулятора - тоже нет. И компилятор такой думает: "А нахера он это сделал? Что он имел в виду? А он ещё и варнинги игнорирует? Тогда пох, сделаю сигнал константой, имею право!".

        Нет там регистра, короче. Был бы - появился бы в списке занятых ресурсов в Total registers. А сейчас там чётко:
        256 шт. cycled_uart_transmitter.Tx +
        8 шт. clk_div_250.count +
        4 шт. cycled_uart_transmitter.i +
        1 шт. clk_div_250.clk_out = 269

        Баланс сошёлся прямо по-бухгалтерски)


        1. Kopcheniy Автор
          16.04.2024 04:57

          Для выяснения распиновки нужно идеально отладить код, чтобы можно было быть уверенным в его верной работе и полагаться на полученные результаты. Пока есть недочёты в виде неоднозначного приёма посылок, конечно, использовать код рано. Но в статье и не предлагается использовать код как есть. Указывается на проблемы неверного приёма посылок, причины и предлагается возможное (и как видится, вполне рабочее) решение: ввести паузы между посылками.

          Способ с UART на каждом выводе вполне можно использовать для определения распиновки. На момент проведения этого эксперимента распиновка уже была найдена генерацией частот (описано в Ч1). Но некоторый интерес в проведении эксперимента оставался.

          Реализация далека от идеала, но идея ясна и может кому-то помочь.

          Хорошо, когда расчёты сходятся.)

          Как бы вы записали код, реализующий подобную (или чётко эту) идею?

          Нет там регистра, короче. Был бы - появился бы в списке занятых ресурсов в Total registers.

          Убедили.) Получается, информационные входы мультиплексора подключены просто к уровням напряжения, соответствующим 0 и 1, а не к выходам регистра (ведь его нет).


          1. alcotel
            16.04.2024 04:57

            Способ с UART на каждом выводе вполне можно использовать для определения распиновки.

            На самом деле идея очень крута. Компилируется это всё за секунды, и не нужно специализированное оборудование за $∞ для тестов. Спасибо, буду пользовать.

            У меня один раз была похожая задачка - собрать тест для многопроводного кабеля между двумя платами с ПЛИС. Я тогда нагенерил PRBS и тестеров к ним. Про UART не догадался, хотя в загашнике уже имелось всё необходимое.


            1. Kopcheniy Автор
              16.04.2024 04:57

              Рад что, на пользу. Вам спасибо за дельные замечания и обратную связь.)

              Под тестерами псевдослучайных последовательностей (ПСП) вы имеете в виду модули, каждый из ктр настроен на приём своей последовательности? Опишите, пожалуйста, подробнее.

              У меня как раз есть удобная программка генератор ПСП (М-последовательностей) с настраиваемыми параметрами. Настраиваем, а на выходе код Verilog/VHDL и сама последовательность.

              Например
              module LFSR8_129(
                input clk,
                output reg [7:0] LFSR = 255
              );
              
              wire feedback = LFSR[7];
              
              always @(posedge clk)
              begin
                LFSR[0] <= feedback;
                LFSR[1] <= LFSR[0];
                LFSR[2] <= LFSR[1];
                LFSR[3] <= LFSR[2] ^ feedback;
                LFSR[4] <= LFSR[3];
                LFSR[5] <= LFSR[4] ^ feedback;
                LFSR[6] <= LFSR[5];
                LFSR[7] <= LFSR[6];
              end
              endmodule
              

              Называется lfsr testbench 1.3

              Смотрю на этот код и вспоминаю, что у меня были проблемы как записать начальное значение для ЮАРТов, чтобы это было удобно для последующей передачи параметра (числа от 0 до 255) в цикле "штамповки" экземпляров передатчика. Пробовал разные варианты. В итоге заработал тот, что в статье. Работает и хорошо.) Менять не стал.

              Мне кажется, что вариант с делением частоты и получением разных частот даже проще. Тоже мог сгодиться для проверки кабеля. Разве что частот поменьше выходит (в Ч1 использовал 22 разных). А Юартов гораздо больше можно сделать.

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


              1. alcotel
                16.04.2024 04:57

                Под тестерами псевдослучайных последовательностей (ПСП) вы имеете в виду модули, каждый из ктр настроен на приём своей последовательности? Опишите, пожалуйста, подробнее.

                Да, как раз через те самые LFSR. Схема скремблер-дескремблер. Если на вход скремблера подать "1", то при отсутствии ошибок на выходе дескремблера тоже будет "1".

                "Полином", который задаёт схему скремблирования - это просто побитовое представление мест в LFSR, где установлен XOR. Поэтому модуль со своим полиномом генерится автоматически в коде, без использования сторонних программ.

                Оставалось только сделать список "хороших" полиномов. И в электронной таблице сгенерить текстовый файл распиновки. Это всё автоматизируется.


            1. Kopcheniy Автор
              16.04.2024 04:57

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