
Здравствуйте меня зовут Дмитрий сегодня мы продолжим исследование FPGA плат. Мы напишем контроллер HDMI интерфейса для Altera Cyclone.
Итак давайте начнем.
HDMI интерфейс
Работа HDMI интерфейса очень похожа на работу VGA интерфейса. Нам также необходимо сформировать 2 сигнала, сигнал синхронизации и сигнал гашения. Тайминги этих сигналов можно узнать на сайте tinyvga.com. Мы как и в случае с VGA выберем 800x600@60 Hz.
always @(posedge clk_40)
begin
if (rst)
begin
Hblank <= 1'b0;
Hsync <= 1'b0;
end
else
begin
case (H_count)
799: Hblank <= 1'b1;
839: Hsync <= 1'b1;
967: Hsync <= 1'b0;
ENDSTRING: Hblank <= 1'b0;
endcase
case (V_count)
599: Vblank <= 1'b1;
600: Vsync <= 1'b1;
604: Vsync <= 1'b0;
ENDFRAME: Vblank <= 1'b0;
endcase
end
end
Но в отличии от VGA используется принципиально иной способ доставки этих сигналов. Сигналы в HDMI интерфейсе передаются последовательно через 4 витых пары. Одна пара это CK через неё передается тактовый сигнал пикселей, одному пикселю соответствует один тик.
И три информационных пары D0, D1, D2. Через D0 передается синий цвет, D1 - зеленый, D2 - красный. Также через D0 передаются синхросигналы. Если рассматривать каждую витую пару отдельно. То данные в них передаются по интерфейсу TMDS.
TMDS
Интерфейс TMDS это дифференциальный интерфейс то есть сигнал в нем фиксируется как разность напряжений двух линий, если на одной линии напряжение больше чем на другой то ноль если меньше то единица. Причем типичная разница напряжений составляет 500 мВ. Да благодаря такому небольшому перепаду напряжений удается достигать очень высоких частот при передачи данных, порядка 10Гбит/с (цифра приблизительная я в справочнике не смотрел).
Ну и как вы понимаете такие высокие частоты ставят нас перед фактом. Наша линия это не просто линия, это длинная линия (то есть четверть длинны волны нашего сигнала меньше чем длина линии).
Конечно про длинные линии можно читать целые лекции. Но я здесь этого делать не буду. Я лишь замечу что для длинной линии важно что-бы либо ближний, либо дальний её конец был согласован по сопротивлению. Причем не важно в какую сторону происходит рассогласование, это может быть нулевое сопротивление или сопротивление в несколько мегаом. Если конец линии не согласован то сигнал будет от него отражается, и постоянно бегая туда сюда как неприкаянный, он будет создавать помехи и ложные срабатывания.

Чтобы согласовать длинную линию нужно на её конце установить сопротивление равное удельному сопротивлению самой длинной линии, в данном случае это 50 Ом. Как видно по схеме у нас согласован дальний конец длинной линии. Причем резистор 50 Ом установлен не между линией и землей, а между линией и шиной питания. Таким образом наша линия мало того что длинная так она ещё и смещена по постоянному напряжению вверх на 3.3 вольта. Для чего это делают? Ну существуют логические вентили которые могут выдерживать намного больший входящий ток, чем исходящий (причем разница может достигать нескольких раз). Но для нас это не имеет никакого значения поскольку пины Cyclone 4 выдерживают только 8 мА как входящего так и выходящего тока.
Кстати резисторы согласующие дальний конец линии имеют такое интересное название. Но я что-то его забыл, там было что-то вроде резистор "Т-1000", но не так, если вы знаете напишите в комментариях.
LVDS

Другой высокоскоростной дифференциальный интерфейс это LVDS. Он очень похож на интерфейс TMDS но имеет несколько отличий. Первое что бросается в глаза это способ согласования на конце длинной линии. Вместо двух резисторов по 50 Ом здесь используется один на 100 Ом но он установлен между дифференциальными линиями. Также отличается уровень питания не 3,3В а 2,5В, и как будто этого мало линия смещена вверх не на весь уровень питания а только на половину то есть 1,25В. Поэтому уровень сигнала в линиях колеблется от 1В до 1,5В.
И вы наверно спросите. А чего это вдруг, я начал рассказывать про какой-то LVDS если я сказал что HDMI использует TMDS? Дак вот плата Cyclone 4 не поддерживает интерфейс TMDS, а поддерживает только LVDS. Поэтому придется подружить эти два интерфейса. Главная проблема это разный уровень смещения по постоянному току у этих интерфейсов. Решить это можно покупкой на Али вот такой вот платы.

В ней для развязки по постоянному напряжению используется конденсатор.

Также пины по которым будут передаваться сигнал нужно перевести в режим LVDS.

Кстати когда я перевел пины в этот режим компилятор начал ругаться на меня из-за того что в одном банке все пины должны иметь одинаковое напряжение питания у LVDS это 2,5В, а у обычных цифровых пинов это 3,3В. Так что будьте готовы к тому что придется пожертвовать целым банком пинов ради нескольких LVDS контактов.
Шифрование
Как я уже сказал сигнал синхронизации передается по линии D0 причем они передается специальными кодами.
wire [9:0] HDMI_code_R = 10'b1101010100;
wire [9:0] HDMI_code_G = 10'b1101010100;
wire [9:0] HDMI_code_B = Vsync ? (Hsync ? 10'b1010101011 : 10'b0101010100) : (Hsync ? 10'b0010101011 : 10'b1101010100);
Сначала нужно сказать что пиксели передаются в инвертированном виде то есть 00000000 это самый яркий пиксель, а 11111111, это черный цвет.
Данные о цвете пикселей также должны быть закодированы особым образом. Это кодирование нужно для балансировки сигнала. Если сигнал будет не сбалансирован. Например в нем будут одни единицы (скажем мы захотели посмотреть по телевизору черный квадрат Малевича) тогда одна из линий будет постоянно нагружена и она может сгореть. Чтобы этого не случилось мы должны стремится к тому чтобы количество нулей в сигнале равнялось количеству единиц.
module DVI_encoder
(
input wire clk,
input wire [7:0] VD,
output reg [9:0] Out_Data = 0
);
// Stage 1: 8 bit -> 9 bit Make "0" more than "1"
wire [3:0] N1_VD = VD[0] + VD[1] + VD[2] + VD[3] + VD[4] + VD[5] + VD[6] + VD[7];
wire flag = (N1_VD > 4'h4) | ((N1_VD == 4'h4) & (VD[0] == 1'b0));
wire [8:0] q_m = flag ? {1'b0, ~VD[7:0]} : {1'b1, VD[7:0]};
// Stage 2: 9 bit -> 10 bit
//Find parity, if number "1" more then "0" then positive parity, else negotyve
wire [3:0] N1_q_m = q_m[0] + q_m[1] + q_m[2] + q_m[3] + q_m[4] + q_m[5] + q_m[6] + q_m[7];
wire [3:0] N0_q_m = 4'h8 - (q_m[0] + q_m[1] + q_m[2] + q_m[3] + q_m[4] + q_m[5] + q_m[6] + q_m[7]);
reg [4:0] Unbulance_count = 'd0;
always @(posedge clk)
begin
Out_Data[8] <= q_m[8];
if((N1_q_m == 4'h4) | (Unbulance_count == 5'h0)) // If we get parity or disbalance counter is zero
begin
Out_Data[9] <= ~q_m[8];
Out_Data[7:0] <= (q_m[8]) ? q_m[7:0] : ~q_m[7:0];
Unbulance_count <= (~q_m[8]) ? (Unbulance_count + N0_q_m - N1_q_m) : (Unbulance_count + N1_q_m - N0_q_m);
end
else
begin
if(Unbulance_count[4]) // If disbalance counter negotive
begin
Out_Data[9] <= 1'b1;
Out_Data[7:0] <= ~q_m[7:0];
Unbulance_count <= Unbulance_count + {q_m[8], 1'b0} + (N0_q_m - N1_q_m);
end
else
begin
Out_Data[9] <=1'b0;
Out_Data[7:0] <=q_m[7:0];
Unbulance_count <= Unbulance_count - {~q_m[8], 1'b0} + (N1_q_m - N0_q_m);
end
end
end
endmodule
На первом этапе мы делаем так чтобы нулей было больше чем единиц, и если оказывается единиц больше то мы инвертируем байт. После чего дописываем девятый бит который говорит был байт инвертирован или нет.
На втором этапе в дело вступает счетчик, который содержит информацию о том насколько сигнал не сбалансирован вообще. И если он отрицательный то это значит что мы отправили слишком много нулей (а как мы помним у нас сейчас нулей больше чем единиц) и нам нужно инвертировать сигнал чтобы единиц было больше. А если положительный то нечего инвертировать не надо и дописываем ещё один бит который говорит инвертировали мы сигнал ещё один раз или нет (всего получается 10 бит). И таким образом мы добиваемся чтобы счетчик стремился к нулю, а сигнал был сбалансирован.
Пересылка сигнала
Как мы узнали из предыдущего раздела для отправки одного пикселя нам нужно последовательно отправить 10 бит. Это значит что биты нужно отправлять на частоте в 10 большей чем частота пикселей. Поскольку у нас частота пикселей 40 мегагерц то для пересылки бит нам нужна частота 400 мегагерц. И тут мы вплотную подошли к пределу частоты PLL Циклона 4, максимальная частота которую он может выдавать это 460 мегагерц.
Но просто так послать сигнал через LVDS пин не получится, для этого в библиотеки Quartus существует специальный модуль LVDS_TX.
Мне повезло я смог уложится в лимит частоты Циклона 4. Но если вы захотите выводить изображение с разрешением больше чем 800x600, тогда можно использовать модуль ALTDDIO. Этот модуль отправляет сигнал через LVDS пин не только по фронту тактового сигнала но ещё и по спаду. Вы наверно слышали про DDR (Double Data Rate) память, она использует такой-же принцип передачи данных. Таким образом частота пересылки бит можно увеличить в два раза до 920 мегагерц.
Вывод
Наверно самым интересным отличием HDMI от VGA, является то что мы "бесплатно" получаем 8-битный цвет. В то время как VGA требует что-бы для каждого бита мы руками припаяли дополнительный резистор к ЦАПу, поэтому редко когда на VGA встречается 8 бит, обычно все ограничиваются четырьмя, что-бы меньше паять.
Для демонстрации я использовал множество Мандельброта из моей предыдущей статьи.
Если вам понравилась эта статья, может вам понравятся и другие мои статьи по FPGA:
Создание видеокарты Бена Итера на FPGA чипе
Доступ к SDRAM памяти на FPGA и «множество Мандельброта»
Написание i2c контроллера для FPGA и подключение камер ov7670 и ov2640
Комментарии (19)
alexo68
18.08.2025 19:34"Если сигнал будет не сбалансирован. Например в нем будут одни единицы ... тогда одна из линий будет постоянно нагружена и она может сгореть."
Нет, не для этого. Линия не сгорит, да и с чего бы ей гореть?
Это нужно, чтобы сигнал проходил через трансформаторную или емкостную (как у вас) развязку. Если в сигнале только единицы или нули, то это эквивалентно наличию постоянной составляющей, которая будет "срезана" при прохождении через трансформатор или емкость.
Flammmable
...или взять TFP410 от Texas Instruments и закидывать в него RGB-данные из ПЛИС (по одному пикселю за клок), а наружу TFP410 будет выдавать честный TMDS.
deema35 Автор
Я проверил в библиотеки Quartus нет такова модуля? Но даже если-бы был, вот представьте нужно написать программу, но нужной библиотеки не найти, что тогда отменяем написание программы? Нет библиотеки нет программы.
deema35 Автор
Мне даже вспомнился анекдот про программистов на Delphi. Перед программистами поставили задачу поймать льва. Все программисты пошли писать свои программы. И только программист на Delphi пошел на форум в интернет спрашивать. Где взять библиотеку которая ловит льва и сажает его в клетку.
Flammmable
Чтобы аналогия была релевантной нашему случаю, надо так:
...в итоге написали программы по ловле таксы, шпица, чихуахуа, при этом сказав "ну, принцип мы показали, осталось чуть масшnабировать"...
...и нашёл :)
Вы никак не сможете сделать честный TMDS на базе ПЛИСа - всё будет "как бы TMDS".
Есть конверторы типа IT6263 и SN65CML100,
но первый вы попробуйте достаньте - на Mouser его например нет, -
а SN65CML100 стоит дороже, чем TFP410, при том, что SN65CML100 - это именно преобразователь LVDS-TMDS один-в-один.
Вы никак не сможете выдать через 4 линии тот битрейт, который требуется для современных разрешений экрана.
В рамках изучения HDMI ваш пример - норм, но если нужно рабочее устройство, альтернативы TFP410 нет.
deema35 Автор
Вы не внимательно прочитали статью. TMDS от LVDS отличаются смещением по постоянному напряжению, поэтому их нельзя подключать напрямую будет короткое замыкание. А интерфейс LVDS также является высокоскоростным как и TMDS, и его скорости хватит с запасом.
Предел скорости этих интерфейсов это гигабиты, а у нас тут лишь сотни мегабит.
Flammmable
...током в линии. Почти в три раза (в пользу TMDS).
Ток в линии TMDS достигает 12 мА, у LVDS - 4,54 мА
При 4-х линиях LVDS-а совокупный битрейт составит 2880 Мбит/с.
Чего "с запасом" хватит на 480p*30 кадров
или может быть как-то хватит на 720p*60 кадров.
Битрейт LVDS составляет порядка 500-700 Мбит/с
TFP410 гарантированно даёт 1080p*60 кадров честного TMDS.
Такие дела.
deema35 Автор
Но мы же передаем не на один метр а только до конденсаторной развязки, а там от силы миллиметров 30.
Flammmable
...а конденсаторная развязка у вас подпаяна прямо к разъёму монитора? :)
Или всё-таки кабелем соединяете?
deema35 Автор
Ну там-то уже TMDS, и на схеме в статье видно что кабель находится на стороне TMDS.
Flammmable
А электроны в метровом кабеле толкает туда-сюда не выходной буфер LVDS, а развязка, которая "превращает LVDS в TMDS", да? )))))
deema35 Автор
Сопротивление контура за конденсатором ниже, если обратится к схеме то там 392 ома а до конденсатора 773 поэтому сила тока будет больше, правда напряжение уменьшится.
deema35 Автор
Ради науки я провел измерения осциллографом на линии CK измерения проводились относительно земли. До конденсатора у нас Синусоида от 1.3В до 1.9В размах 0.6. После конденсатора синусоида от 2.85В до 3.36В размах 0.51. Ну видимо не слишком сильно меняется сила тока.
Flammmable
Это не модуль, это микросхема.
KeisN13
Интересно это каким образом будет отличаться от вга? Это не тру
Flammmable
Критерии тру/не_тру?