Приближается Новый Год! Мы поздравляем читателей нашего сайта с этим праздником! НО, сегодня нам нужен Новогодний FPGA Проект. У нас уже чего только не было на сайте: и двигалось и светило и моргало и показывало. Но, что поделать, самое новогоднее, что можно придумать - это гирлянды, бегущие огни, светодиодная лента. Их тоже мы уже подключали: был проект простого управления светодиодной лентой и мы ее устанавливали в виде импровизированной елочки и потом мы даже сделали цветомузыку на светодиодной ленте.
Теперь пришла идея сделать.. саму светодиодную ленту. Простая светодиодная лента состоит из последовательно соединенных RGB микросхем, например, вот таких: WS2812B. Соединяются они тремя проводами: питание, земля и информационный сигнал, по которому передается код цвета для каждого пикселя индивидуально.
Идея проекта состоит в следующем. Я попытаюсь сделать "реверс инжиниринг" микросхемы WS2812B и реализовать всю ее логику внутри простой ПЛИС платы Марсоход с Altera MAX II. Потом я соединяю последовательно несколько плат Марсоход и подключаю их прямо к светодиодной ленте, таким образом, делаю ленту длиннее на несколько пикселей. Мои ПЛИСы становятся продолжением ленты и светодиоды, подключенные к ПЛИС, должны светиться точно так же, как сама лента. Вообще-то платы Марсоход пришлось немного модернизировать: у них штатно стоят 8 желтых светодиодов, а мы заменяем 3 из них на цветные R, G, B. Это не очень большая переделка.
Наверное кто-то скажет, что это бессмысленный проект, зачем делать то, что уже кем-то сделано. Но с другой стороны - а почему бы и нет? У нас же образовательный сайт. Вот поучимся копировать логику существующих микросхем в FPGA, благо логика очень простая. Ну и в конце концов, может какой нибудь светодиодный завод в России захочет производить подобные чипы где нибудь в Зеленограде? Мне даже кажется, что в этом есть какой-то смысл: для изготовления светодиодных лент и видео стен требуются сотни, тысячи и миллионы таких микросхем... Для одного FullHD светодиодного экрана, если бы его изготавливали из светодиодной ленты, нужно 2 миллиона RGB микросхем. Оптовое изготовление всегда снижает итоговую стоимость производства.
Конечно я немного упрощаю. Микросхема WS2812B возможно не такая простая, как я тут расписываю. В ней есть встроенный генератор, видимо RC-генератор, и он должен быть весьма стабильным вне зависимости от внешней температуры. Но я про это сейчас даже думать не буду. Мой проект немного упрощенный, я буду эмулировать микросхему WS2812B в нашей простой плате Марсоход и на плате есть кварцевый генератор 100МГц. Его и буду использовать для измерения интервалов времени.
Итак, поехали.
Описание микросхемы WS2812B можно взять здесь:
Микросхема работает довольно просто. Микросхемы в ленте соединены цепочкой и биты передаются последовательным кодом. Биты ноль в потоке - это узкие импульсы, а биты единицы в потоке - широкие.
Алгоритм работы может быть такой:
- Находим перепад с нуля на единицу.
- Измеряем временной интервал в половину длины бита, смотрим, что в этот момент зафиксировалось - ноль, значит приняли ноль, а единица - значит приняли единицу.
- Принятый бит задвигается в приемный сдвиговый регистр.
- Каждый раз при фиксации нового принятого бита увеличиваем счетчик принятых бит.
- Как только принято 24 бита в этом чипе WS2812B прием здесь завершается и входной битовый поток переключается на вывод к следующей микросхеме в цепочке. До этого она не получала никакого кода, на выходе предыдущего чипа был ноль.
- Если линия находится в нуле достаточно долго, то из сдвигового регистра значения яркостей RGB переписываются в регистр ШИМ, из него формируются Широтно-Импульсно Модулированные сигналы для светодиодов R-G-B.
Код Verilog HDL, который описывает всю эту логику может выглядеть вот так:
`timescale 1ns / 1ns module WS2812B( input wire clk, input wire in, output wire out, output wire [23:0]q, output reg r, output reg g, output reg b ); localparam reset_level = 3000; localparam fix_level = 50; //reg clk = 0; //always #26 clk = ~clk; //capture "in" signal into shift register reg [1:0]r_in = 0; always @( posedge clk ) r_in <= { r_in[0],in }; //detect zero-to-one transition in "in" signal via shift register wire in_pos_edge; assign in_pos_edge = (r_in==2'b01); //count how long "in" signal stays in zero reg [15:0]reset_counter = 0; always @( posedge clk ) if( r_in[0] ) reset_counter <= 0; else if( reset_counter<reset_level ) reset_counter <= reset_counter+1; //if "in" signal stays in zero for long time -> reset condition wire reset; assign reset = (reset_counter==reset_level); //every zero-to-one signal "in" transition mean start of new bit reg [7:0]bit_length_cnt; always @( posedge clk ) if( in_pos_edge ) bit_length_cnt <= 0; else if( bit_length_cnt<(fix_level+1) && !pass ) bit_length_cnt <= bit_length_cnt + 1; //get impulse of bit capture wire bit_fix; assign bit_fix = (bit_length_cnt==fix_level); reg pass = 0; reg [5:0]bits_captured = 0; //count number of bits captured always @( posedge clk ) if( reset ) bits_captured <= 1'b0; else if( ~pass && bit_fix ) bits_captured <= bits_captured+1'b1; //after capturing 24 bits this chip is locked and pass input to output always @( posedge clk ) if( reset ) pass <= 1'b0; else if( bits_captured==23 && bit_fix ) pass <= 1'b1; //actual pass after last bit receive (falling edge) reg pass_final; always @( posedge clk ) if( reset ) pass_final <= 1'b0; else if( r_in!=2'b11 ) pass_final <= pass; //accumulating shift register for RGB bits reg [23:0]shift_rgb; always @( posedge clk ) if( bit_fix ) shift_rgb <= { in, shift_rgb[23:1] }; //final capture register for RGB bits reg [23:0]fix_rgb; always @( posedge clk ) if( bits_captured==23 && bit_fix ) fix_rgb <= { in, shift_rgb[23:1] }; //when this chip captured 24 bits of RGB it becomes transparent //and passes all input to output without change assign out = pass_final ? in : 1'b0; assign q = fix_rgb; wire [7:0]wgreen; assign wgreen = { fix_rgb[0 ], fix_rgb[1 ], fix_rgb[2 ], fix_rgb[3 ], fix_rgb[4 ], fix_rgb[5 ], fix_rgb[6 ], fix_rgb[7 ] }; wire [7:0]wred; assign wred = { fix_rgb[8 ], fix_rgb[9 ], fix_rgb[10], fix_rgb[11], fix_rgb[12], fix_rgb[13], fix_rgb[14], fix_rgb[15] }; wire [7:0]wblue; assign wblue = { fix_rgb[16], fix_rgb[17], fix_rgb[18], fix_rgb[19], fix_rgb[20], fix_rgb[21], fix_rgb[22], fix_rgb[23] }; //pulse-width-modulation reg [7:0]pwm_cnt; always @( posedge clk ) begin pwm_cnt <= pwm_cnt+1; r <= pwm_cnt<wred; g <= pwm_cnt<wgreen; b <= pwm_cnt<wblue; end endmodule
Как видите, тут всего пара страниц текста, что совсем не много. Исходный код Verilog снабжен комментариями, которые все поясняют. Они на английском, но, надеюсь это не проблема для наших читателей.
Чтобы было более понятно, я приведу фото соединений плат поближе:
А вот схематичное изображение соединений:
Пин F5 платы марсоход служит входом для последовательных данных, а пин F2 - выход данных.
Вот они и соединены цепочкой [LED TAPE]->[F5,F2]->[F5, F2]->[F5, F2]->..
Несмотря на то, что на моей ленте питание +5В, я не стал подключать платы прямо к этому питанию. Максимальное напряжение для ПЛИС MAX II всего +4,6В, поэтому я питаю всю цепочку плат одним дополнительным источником, аккумулятором. У аккумулятора и ленты сигналы "Земля" соединены вместе.
При желании можно просимулировать всю цепочку "чипов". Для этого нужен Verilog HDL тестбенч:
`timescale 1ns / 1ns module LED_tape_TB; // Inputs reg clk = 0; always begin #206; clk = ~clk; end // Outputs wire data; wire [15:0] w_num; wire w_req; wire w_sync; reg [1:0]r_sync =0; always @(posedge clk ) r_sync <= { r_sync[0], w_sync }; reg [7:0]color = 8'h31; always @(posedge clk ) if( r_sync==2'b01 ) color<=color+1; reg [23:0]rgb = 0; always @(posedge clk ) if( w_req ) rgb <= { 8'hA5, 8'hB6, color }; // Instantiate the LED TAPE control Unit LED_tape #( .NUM_LEDS(7), .NUM_RESET_LEDS(10) )uut ( .clk(clk), .RGB(rgb), .data(data), .num(w_num), .sync(w_sync), .req(w_req) ); reg clk100 = 0; always begin #5; clk100 = ~clk100; end wire out0; wire [23:0]q0; WS2812B WS2812B_0( .clk( clk100 ), .in( data ), .out( out0 ), .q( q0 ), .r(), .g(), .b() ); wire out1; wire [23:0]q1; WS2812B WS2812B_1( .clk( clk100 ), .in( out0 ), .out( out1 ), .q( q1 ), .r(), .g(), .b() ); wire out2; wire [23:0]q2; WS2812B WS2812B_2( .clk( clk100 ), .in( out1 ), .out( out2 ), .q( q2 ), .r(), .g(), .b() ); wire out3; wire [23:0]q3; WS2812B WS2812B_3( .clk( clk100 ), .in( out2 ), .out( out3 ), .q( q3 ), .r(), .g(), .b() ); initial begin $dumpfile("out.vcd"); $dumpvars(0,LED_tape_TB); #2000000; $finish(0); end endmodule
Здесь в тестбенче установлено несколько экземпляров WS2812B_0, WS2812B_1, WS2812B_2, WS2812B_3. Выход первого чипа out0 служит входом для второго, из второго выходит out1 и заходит в третий. Так и получается цепочка. Запустить симулятор Icarus Verilog можно следующими командами:
>iverilog -o qqq WS2812B.v LED_tape_TB.v LED_tape.v
>vvp qqq
>gtkwave out.vcd
На временных диаграммах сигналов можно посмотреть, как происходит передача данных от чипа к чипу:
Исходные данные data поначалу видит только первый в цепочке "чип" WS2812B_0. Остальные видят на входных линиях только ноль. Как только первый чип выберет из входного потока свои положенные 24 R-G-B бита у него внутри взводится регистр pass_final и входные данные перенаправляются на выход. Теперь уже второй "чип" WS2812B_1 видит поток и он может выбрать из него свои собственные 24 RGB бита. Теперь уже второй чип "насытился", у него устанавливается внутренний pass_final регистр и он передает эстафету следующему.
Когда на линии длинная пауза все чипы это видят, защелкивают принятые данные в регистры для ШИМ и возвращаются в исходное состояние. Опять первый чип готов выбирать свои 24 бита. Так и работает настоящая лента.
Видео демонстрация в начале статьи показывает, как все происходит. Задействована плата Марсоход3 в качестве источника сигнала для светодиодной ленты. FPGA проект в плате Марсоход3 сперва зажигает (с мягким нарастанием и затуханием) все синие светодиоды, потом все красные, потом все зеленые, ну и затем чередует цвета. К ленте подключено последовательно еще 4 платы Марсоход, которые являются прототипами микросхемы WS2812B. Видно, что светодиоды моих плат работают так же как светодиоды ленты. Таким образом мне удалось воссоздать логику работы микросхемы WS2812B в простой ПЛИС Altera MAX II платы Марсоход.
Весь проект Intel Quartus Prime можно взять у нас на странице github: https://github.com/marsohod4you/WS2812B-FPGA-Emulation-
Там есть папка max10-led-tape-control - проект Quartus Prime для платы Марсоход3 (чип MAX10), как источника сигнала для светодиодной ленты, так и проект Quartus для CPLD MAX II платы Марсоход в папке max2.
Подробнее...