Генератор цветных полос на FPGA

Цветные полосыЭта статья рассказывает, как генерировать VGA развертку и как на отладочной плате DE2-115 от компании terasIC сделать VGA генератор цветных полос. Это очень простой проект, который подходит для начинающих.

Для начала, давайте посмотрим, какие сигналы требуются, чтобы сформировать картинку.

Конечно, это информация о цвете — три аналоговых сигнала красного (R), зеленого (G) и синего (B) цветов с размахом от 0.7 до 1 В. Т.к. у меня плата DE2-115, а на ней есть video ЦАП, то я буду использовать его. Во-вторых — сигналы вертикальной и горизонтальной синхронизации (vsync и hsync), они цифровые и тактовый сигнал (pixel clock).

Будем использовать разрешение экрана 1680 x 1050 @ 60 Гц, импульс горизонтальной синхронизации отрицательной полярности, вертикальной — положительной. В данном случае pixel clock = 147.14 МГц. Я постарался наглядно изобразить тайминги на рисунке ниже.

Тайминги VGA

Отрисовка строки начинается с импульса горизонтальной синхронизации (hsync) с длительностью 1.25 мкс, далее следует промежуток в 1.96 мкс в течении которого выходы RGB заперты, после него передается цветовая информация — каждый пиксель за один тик и, наконец, пауза длительностью 0.71 мкс в течении которой происходит обратных ход луча на ЭЛТ мониторах.

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

Как я уже говорил, я буду использовать плату DE2-115.

Итак, нам нужен генератор pixel clock 147.14 МГц. Тактовый генератор, установленный на плате, работает на частоте 50 МГц, поэтому будем использовать синтезатор частоты PLL. В Cyclone IV EP4CE115 их 4 штуки. Генератор развертки и формироватьель полос вынесем в отдельные модули.

Как видно из задачи, для генерации разверки нам потребуется два таймера. Один будет увеличиваться на 1 при каждом тике pixel clock от 0 до суммы всех временных интервалов строки (pulse + back porch + visible area + front porch). А второй будет работать аналогично, но увеличиваться при переднем фронте сигнала hsync. За ноль таймера возьмем начало интервала Front porch.

module vga_sync_generator(pixel_clk,    
                                   hsync,
                                    vsync,
                                    blank_N,
                                    pixel_clk_N);
 
// Параметры для горизонтальной синхронизации
parameter H_ACTIVE_VIDEO = 1680;
parameter H_FRONT_PORCH = 104;
parameter H_SYNC_PULSE = 184;
parameter H_BACK_PORCH = 288;
parameter H_BLANK_PIX = H_FRONT_PORCH + H_SYNC_PULSE + H_BACK_PORCH;
parameter H_TOTAL_PIX = H_ACTIVE_VIDEO + H_BLANK_PIX;
 
// Параметры для вертикальной синхронизации
parameter V_ACTIVE_VIDEO = 1050;                            
parameter V_FRONT_PORCH = 1;
parameter V_SYNC_PULSE = 3;
parameter V_BACK_PORCH = 33;
parameter V_BLANK_PIX = V_FRONT_PORCH + V_SYNC_PULSE + V_BACK_PORCH;
parameter V_TOTAL_PIX = V_ACTIVE_VIDEO + V_BLANK_PIX;
                                     
input pixel_clk;
 
output hsync;
output vsync;
output blank_N;
output pixel_clk_N;
 
// счетчики
reg [10:0] countV;
reg [11:0] countH;
 
assign pixel_clk_N = ~pixel_clk; // инвертированный PixelClock
assign blank_N = ~((countV < V_BLANK_PIX) || (countH < H_BLANK_PIX)); // Blank сигнал
 
// импульс вертикальной синхронизации
assign vsync = (countV >= V_FRONT_PORCH-1) && (countV <= V_FRONT_PORCH + V_SYNC_PULSE-1);
 
// импульс горизонтальной синхронизации
assign hsync = ~((countH >= H_FRONT_PORCH-1) && (countH <= H_FRONT_PORCH + H_SYNC_PULSE-1));
 
always @(posedge pixel_clk)
begin
    if (countH < H_TOTAL_PIX)
        countH <= countH + 1'b1;
    else
        countH <= 0;
end
 
always @(posedge hsync)
begin
    if (countV < V_TOTAL_PIX)
        countV <= countV + 1'b1;
    else
        countV <= 0;
end
endmodule


Далее создадим модуль, отрисовывающий цветные полосы. Для этого нам нужены тактовые испульсы pixel clock. И сигнал blank, для синхронизации.

Давайте посмотрим на последовательность полос:

белый — желтый — голубой — зеленый — пурпурный — красный — синий — черный.

Итого 8. Значит на одну полосу приходится 1680 / 8 = 210 пикселей. Теперь разложим цвета на составляющие

  • бел (R = 0xFF, G=0xFF, B=0xFF)
  • желт. (R = 0xFF, G = 0xFF, B = 0x00)
  • голуб. (R = 0x00, G = 0xFF, B = 0xFF)
  • зелен. (R = 0x00, G = 0xFF, B = 0x00)
  • пурпур. (R = 0xFF, G = 0x00, B = 0xFF)
  • красн. (R = 0xFF, G = 0x00, B = 0x00)
  • синий. (R = 0x00, G = 0x00, B = 0xFF)
  • черн. (R = 0x00 G = 0x00, B = 0x00)
братите внимание на порядок следования 0x00 и 0xFF для разных цветов. Ничего не напоминает? Да это же 3-х битный счетчик.
Поэтому сигналы RGB очень просто записать так:

wire [7:0] blue     = color[0] ? 8'hFF : 8'h0;
wire [7:0] red      = color[1] ? 8'hFF : 8'h0;
wire [7:0] green    = color[2] ? 8'hFF : 8'h0;


где color — это счетчик [2:0]. Еще надо учесть, что во время импульса blank нам надо запирать выходы RGB. Если этого не сделать, ЖК мониторы не смогут автоматически определить границу растра и выровнять его. Вот так:

1	output wire [23:0] rgb = blank ? {blue[7:0], green[7:0], red[7:0]} : 24'b0;


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

Вот конечный код:

module bars_generator(clk_pixel, blank, rgb);
 
input clk_pixel;
input blank;
 
reg [7:0] bar;
reg [2:0] color;
 
wire [7:0] blue     = color[0] ? 8'hFF : 8'h0;
wire [7:0] red      = color[1] ? 8'hFF : 8'h0;
wire [7:0] green    = color[2] ? 8'hFF : 8'h0;
  
output wire [23:0] rgb = blank ? {blue[7:0], green[7:0], red[7:0]} : 24'b0;
 
always @(posedge clk_pixel)
begin
    if (!blank)
    begin
        bar <= 8'b0;
        color <= 3'b111;
    end
        else if (bar < 8'd210)
                bar <= bar + 1'b1;
                else
                    begin
                        bar <= 8'b0;
                        color <= color - 1'b1;
                    end
         
         
end
 
endmodule


Вот и весь собственно генератор.

Теперь соединим все воедино, для наглядности в диаграмме:

Блок-диаграмма

Синтезируем (вышло 60 LE) и зальем в плату. Получилось!

Генератор полос в работе

Благодарю за внимание!

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

RSS свернуть / развернуть
На фото левая плата расширения от waveshare? под какой мк?
0
  • avatar
  • ZiB
  • 05 декабря 2012, 11:41
Да, на фото плата Open407V-D. Она под STM32F4DISCOVERY. Справа от монитора набор модулей к ней.
0
Сам посматривал как раз под ф4, где покупал?
Какие впечатления?
Модули отдельно или все сразу?
0
напрямую у производителя, все в комплекте.
Плата так себе. Единственное что все сгруппировано по интерфейсам. В остальном — колодки дублируют расположение на самой DISCOVERY-плате, есть «типа» джойстик — лучше бы 4 кнопки поставили. Не хватает индикаторов — светодиодов.
0
Все класс, только одна проблема — цена платки. 299$ при наличии студенческого билета + доставка (порядка $50) выйдет. Да еще вырвать из цепких лап таможни… Для нашего студента — дороговато будет. Вот поэтому мы и торчим все в жопе.
0
Бывает, когда кафедра покупает для занятий. Только одной-двух отладок на группу, конечно… не разгонишься.)
0
Да, да. Очень печально. Я сам очень удивился, когда прочитал на этом портале о проблемах с таможней.
Вон та же Альтера на будущее работает, хоть в Зимбабве продадут по студентческой. А прибыль от этого — маловероятна и непонятно как по срокам. Но люди задумываются о будущем, об образовании. А у нас правительство все ломает на корню. Вот мы, например, в Казахстане хотим выпускать свои продукты… и не можем официально завозить радиодетали и платы (пустые) из-за налогов. А потом там наверху что-то еще про патриотизм речи толкают, выгодно видимо, чтобы страна в заднице была.

Но у нас в Казахстане про эту плату таможня промолчала.
А Terasic'у огромное спасибо! Я в открытую им сказал, что я не студент, а просто любитель и хочу расширить кругозор, получить новые знания и опыт. Т.е. толку от меня не будут никакого — я больше ничего не буду заказывать. И меня не послали! Правда, как объяснили, сканы студенческих нужны для отчета Альтере, с этим мне помог друг, большое ему спасибо! В итоге я купил плату по студенческой цене, и Terasic знали, что продают не студенту.
Поэтому кто хочет заказать — без лишних вопросов сразу заливайте билет друга в аккаунт, ничего говорить не надо, они сами все прекрасно понимают. С менеджерами очень приятно разговаривать — обслуживание просто на высоте!

Из недорогих вариантов есть платы от той же Waveshare. Нпример можно купить модуль c Циклоном 4 за 28 долларов. (CoreEP4CE6. Конечно это далеко не DE2, но поверьте, 6К LE это не мало, особенно для начала.
0
кстати, правда, что в России если осциллограф покупаешь, то его в любом случае растомаживать нужно будет, как спец. оборудование?
0
Правда, но частично. Брал месяц назад hantek dso5102b на ebay. Приехало за 25 дней. Повезло. В 90% прокатывает. Все зависит от того какой код классификации поставит таможенник.
0
У нас в КПИ (Киев), такие вещи покупаются под крышей кафедры через юниверсити програм (сразу с таможней проблем становится значительно меньше), студент потом ею играется сколько хочет, а потом по окончании обычно отдает кафедре для последующих поколений.
0
поршни от немца? =) лично угробили? =)
0
:) Не, это от Тойоты Превии (2TZFE вроде). Угробили на работе, я разобрал ради интереса.
0
Поршни — это вон те железные хреновины, расположенные как колонки? А меня вот интересует, что за устройство справа от левого поршня, плоское такое.
0
Это считыватель карт ЭЦП (для банка)
0
Ого! У нас в Ате есть такие спецы! не знал=) над познакомиться будет=)
0
  • avatar
  • dixis
  • 05 декабря 2012, 19:10
Спасибо, но FPGA я пока только учусь :). Конечно, а то скучно без единомышленников!
0
А вот в цифровых выходах, типа hdmi, там тоже есть эти задержки для возврата луча ЭЛТ или уже нет? Например, преобразователь hdmi в vga можно ли представить просто как голый ЦАП, который никаких задержке не добавляет, и ничего буферизовать ему не надо?
0
  • avatar
  • ks_
  • 15 декабря 2012, 21:23
Боюсь вам наврать, т.к. с темой DVI/HDMI не разбирался, так поверхностно.
Там по таймингам все осталось почти также (в смысле присутствуют blank периоды), но в эти промежутки вместо видеоданных передается сервисная информация и звук, так называемые «data island» блоки.
0
Задержка для возврата в DVI/HDMI не нужна, но она будет присутствовать. Для не видео сигналов, тот же звук.
Поэтому если вставить ЦАП и подать ему поток с DVI (не забыв вычислить HSYNC VSYNC из синего канала, и звук на обратном ходе), то на выходе будет VGA совместимый (по времянкам) сигнал.

Более того, в DVI сериализатор поступает как раз VGA сигнал (RGB + HSYNC VSYNC + звук) и затем сериализуется TMDS.

Но, справедливости ради надо сказать бывают цифровые видеосигналы без обратного хода луча. Используются для уменьшения частоты при том же потоке, или улучшения картинки при той же полосе.
0
бывают цифровые видеосигналы без обратного хода луча
GSync от NVIDIA как раз на этом основана, да и AMD намекает что это давно в стандарте есть.
0
Здравствуйте неделю назад получил платку от Terasic De1-Soc, т.к. с плис дел некогда не имел сначало поигрался с логикой сегментниками и кнопочками. Решил попробовать ваш проект и вот второй день втыкаю, не могу понять где затык. Использую чуть другое разрешение (сначало пробовал и с вашим тоже глухо) 640х480 все чиселки поменял по таймингам и по пикселям на полосу, а оно все равно ни в какую. Если кому не трудно может я где-то какую-то мелочь упустил и в упор не вижу?
Сам проект rghost.ru/53311339
У кого опыт есть в чем может быть ошибка по сути ведь видеодак одининаковый…?
0
Здравствуйте, уважаемый Karsakbayev! Разрешите обратиться к Вам с вопросом. Опробовал Ваш проект на плате DE1 с Cyclone II. Проект заработал без особых проблем. Но интересно вот что.

В Вашем проекте, в модуле bars_generator есть объявление трех цепей:

wire [7:0] blue = color[0]? 8'hFF: 8'h0;
wire [7:0] red = color[1]? 8'hFF: 8'h0;
wire [7:0] green = color[2]? 8'hFF: 8'h0;

Этим цепям присваиваются определенные значения (8'hFF или 8'h0) в зависимости от значения битов в регистре color. Причем, к примеру, цепи red будет присвоено значение 8'hFF в случае, если бит 1 в регистре color будет равен лог. 1 (color[1]=1). В принципе, можно было бы присваивать цепи red значение 8'hFF в случае, если бы бит 2 в регистре color был бы равен лог. 1 (color[2]=1). Тогда цепь red была бы объявлена так:

wire [7:0] red = color[2]? 8'hFF: 8'h0;

а две другие допустим так:

wire [7:0] blue = color[0]? 8'hFF: 8'h0;
wire [7:0] green = color[1]? 8'hFF: 8'h0;

Теоретически, это бы повлияло только на последовательность полос на мониторе. Но на практике эта замена приводит к неработоспособности проекта. Я заметил, что такое изменение кода влияет на значение частоты импульсов vsync. Частота в моем проекте увеличилась более чем вдвое. На монитор выводится постоянно сплошной черный цвет. Программа работает только при определенной комбинации вышеуказанных битов.
Собственно вопрос: как связаны vsync и объявление этих цепей? В чем зависимость?
0
Добрый день, Алексей!

В примере, там где происходит генерация синхроимпульсов есть небольшая ошибка. Сейчас навскидку не скажу, будет время — гляну.
0
Буду очень ждать!
0
Я все еще жду…
0
Опробовал Ваш проект на плате DE1 с Cyclone II. Проект заработал без особых проблем.
PLL Cyclone II можно настроить 147.14 МГц? Или Ваш проект для другого разрешения экрана?
0
В своем проекте я использовал разрешение 1280х1024 с пиксельной частотой 108 МГц. Кстати, ответы на свои вопросы я нашел. Умные люди подсказали в чем кривость программы в этой статье. Ответа от Karsakbayev не дождался.
+1
Кстати, ответы на свои вопросы я нашел.
Нашел сам — поделись с другими.
0
Видимо не дождусь…
0
Программа в статье работает случайно — для определенного разрешения и именно такого порядка полос. Убил два дня — проблема в неправильном запуске блока countV.
Для исправления блок countH переписать, аналогичный блок always @(posedge hsync) удалить:


always @(posedge pixel_clk)
begin
    if (countH < H_TOTAL_PIX)
        countH <= countH + 1'b1;
    else
    begin
       countH <= 0;
       if (countV < V_TOTAL_PIX)
         countV <= countV + 1'b1;
       else
         countV <= 0;
    end
end
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.