
Halo. Sudah hampir dua tahun yang lalu, saya membeli kit berbahasa Mandarin di aliexpress, yang terdiri dari papan debug EasyFPGA A2.2 dengan papan Cyclone IV EP4CE6E22C8N, remote control IR SE-020401, programmer, sepasang kabel USB dan loop. Untuk waktu yang lama semua hal ini tidak berguna bagi saya, tk. Saya tidak dapat memikirkan tugas yang menarik dan tidak terlalu memakan waktu untuk diri saya sendiri.
Tahun lalu, di aliexpress yang sama, saya memesan strip LED RGB berdasarkan sirkuit mikro WS2811 yang terkenal. Sebelum membeli, setelah melihat ulasan YouTube tentang protokol spesifik sirkuit mikro ini, saya memutuskan bahwa akan menarik untuk menulis driver saya sendiri untuk mereka untuk FPGA. Dan sejak itu papan tersebut memiliki photodetector, kemudian Anda juga dapat menambahkan kemampuan untuk mengklik mode dengan remote control dari kit. Proyek akhir pekan sebelum Tahun Baru.
Bekerja dengan WS2811
Faktanya, dari datasheet di WS2811 dapat dilihat bahwa protokolnya cukup sederhana: 24 bit data warna dalam format RGB888 MSB-first harus dikirim ke output DIN dari sirkuit mikro. Sirkuit mikro akan menduplikasi 24 bit berikutnya dari data yang diterima pada pin DOUT, yang memungkinkan WS2811 di-daisy-chain:
Diagram koneksi serial sirkuit mikro WS2811:

DIN . — 1.2 µs 1.3 µs, — 0.5 µs 2.0 µs . — 2.5 µs. 50 µs, OUTR ,OUTG OUTB, .
WS2811:

WS2811 WS2811Transmitter
module WS2811Transmitter
# (
CLOCK_SPEED = 50_000_000
)
(
input clkIN,
input nResetIN,
input startIN,
input [23:0] dataIN,
output busyOUT,
output txOUT
);
localparam DIVIDER_100_NS = 10_000_000; // 1 / 0.0000001 = 10000000
reg [4:0] cnt100ns;
reg [24:0] dataShift;
reg busy;
reg tx;
wire [24:0] dataShifted = (dataShift << 1);
wire clock100ns;
initial begin
busy = 0;
tx = 0;
cnt100ns = 5'd0;
end
assign busyOUT = busy;
assign txOUT = tx;
ClockDivider #(.VALUE(CLOCK_SPEED / DIVIDER_100_NS)) clock100nsDivider (
.clkIN(clkIN),
.nResetIN(busy),
.clkOUT(clock100ns)
);
always @(negedge clkIN or negedge nResetIN) begin
if (!nResetIN) begin
busy <= 0;
tx <= 0;
cnt100ns <= 5'd0;
end
else begin
if (startIN && !busy) begin
busy <= 1;
dataShift <= {dataIN, 1'b1};
tx <= 1;
end
if (clock100ns && busy) begin
cnt100ns <= cnt100ns + 5'd1;
if (cnt100ns == 5'd4 && !dataShift[24]) begin
tx <= 0;
end
if (cnt100ns == 5'd11 && dataShift[24]) begin
tx <= 0;
end
if (cnt100ns == 5'd24) begin
cnt100ns <= 5'd0;
dataShift <= dataShifted;
if (dataShifted == 25'h1000000) begin
busy <= 0;
end
else begin
tx <= 1;
end
end
end
end
end
endmodule, clock100nsDivider 100 ns, clock100ns cnt100ns . startIN 1, , 1 busyOUT. txOUT , 12 cnt100ns 5 — txOUT . 25 , 24 , busyOUT 0.
, clkIN. , busyOUT.
24 FF0055h WS2811Transmitter:

NEC Infrared Transmission Protocol. 562.5µs 562.5µs. — 562.5µs 1.6875ms . — 9ms 4.5ms . 562.5µs .
: (9ms 4.5ms ), 8 , 8 — , 8 — , 8 562.5µs . LSB-first.
NEC Infrared Transmission :

NEC NecIrReceiver
module NecIrReceiver
# (
CLOCK_SPEED = 50_000
)
(
input clkIN,
input nResetIN,
input rxIN,
output dataReceivedOUT,
output [31:0] dataOUT
);
localparam DIVIDER_281250_NS = 3556; // 562.5µs / 2 = 281.25µs; 1 / 0.00028125 ≈ 3556
reg [23:0] pulseSamplerShift;
reg [33:0] dataShift;
reg [31:0] dataBuffer;
reg [1:0] rxState;
reg rxPositiveEdgeDetect;
reg clock281250nsParity;
reg clock281250nsNReset;
wire clock281250ns;
wire startFrameReceived;
wire dataPacketReceived;
initial begin
rxState = 2'd0;
rxPositiveEdgeDetect = 0;
clock281250nsParity = 0;
clock281250nsNReset = 0;
pulseSamplerShift = 24'd0;
dataShift = 34'd0;
dataBuffer = 32'd0;
end
assign dataReceivedOUT = rxState[0];
assign dataOUT = dataBuffer;
assign dataPacketReceived = dataShift[32];
assign startFrameReceived = dataShift[33];
ClockDivider #(.VALUE(CLOCK_SPEED / DIVIDER_281250_NS)) clock281250nsDivider (
.clkIN(clkIN),
.nResetIN(clock281250nsNReset),
.clkOUT(clock281250ns)
);
always @(posedge clkIN or negedge nResetIN) begin
if (!nResetIN) begin
rxState <= 2'd0;
rxPositiveEdgeDetect <= 0;
clock281250nsParity <= 0;
clock281250nsNReset <= 0;
pulseSamplerShift <= 24'd0;
dataShift <= 34'd0;
dataBuffer <= 32'd0;
end
else begin
case ({dataPacketReceived, rxState[1:0]})
3'b100 : begin
dataBuffer[31:0] <= dataShift[31:0];
rxState <= 2'b11;
end
3'b111, 3'b110 : rxState <= 2'b10;
default : rxState <= 2'd0;
endcase
case ({rxIN, rxPositiveEdgeDetect})
2'b10 : begin
rxPositiveEdgeDetect <= 1;
clock281250nsParity <= 0;
clock281250nsNReset <= 0;
pulseSamplerShift <= 24'd0;
case ({startFrameReceived, dataPacketReceived, pulseSamplerShift})
26'h0ffff00 : dataShift <= 34'h200000001;
26'h2000002 : dataShift <= {1'd1, dataShift[31:0], 1'd0};
26'h2000008 : dataShift <= {1'd1, dataShift[31:0], 1'd1};
default : dataShift <= 34'd0;
endcase
end
2'b01 : rxPositiveEdgeDetect <= 0;
endcase
if (clock281250nsNReset == 0) begin
clock281250nsNReset <= 1;
end
if (clock281250ns) begin
clock281250nsParity <= ~clock281250nsParity;
if (!clock281250nsParity) begin
pulseSamplerShift <= {pulseSamplerShift[22:0], rxIN};
end
end
end
end
endmodule562.5µs. pulseSamplerShift rxIN 562.5µs. .. , ClockDivider — 281.25µs. clock281250ns clock281250nsParity, . rxPositiveEdgeDetect , pulseSamplerShift , .
00FF0FF0h NecIrReceiver:

Main
module Main
(
input clkIN,
input nResetIN,
input rxIN,
output txOUT
);
localparam IR_COMMAND_EQ = 32'h00ff906f;
localparam IR_COMMAND_PLAY = 32'h00ffc23d;
localparam IR_COMMAND_PREV = 32'h00ff22dd;
localparam IR_COMMAND_NEXT = 32'h00ff02fd;
localparam IR_COMMAND_MINS = 32'h00ffe01f;
localparam IR_COMMAND_PLUS = 32'h00ffa857;
localparam UNITS_NUMBER = 100;
localparam PATTERN_COLORS_NUMBER = 128;
localparam PATTERNS_NUMBER = 4;
localparam CLOCK_SPEED = 50_000_000;
localparam UPDATES_PER_SECOND = 20;
reg [$clog2(PATTERNS_NUMBER) - 1:0] patternIndex;
reg [$clog2(PATTERN_COLORS_NUMBER) - 1:0] colorIndex;
reg [$clog2(PATTERN_COLORS_NUMBER) - 1:0] colorIndexShift;
reg colorIndexShiftDirection;
reg [2:0] colorSwapIndex;
reg [$clog2(UNITS_NUMBER) - 1:0] unitCounter;
reg txStart;
reg pause;
reg beginTransmissionDelay;
wire ws2811Busy;
wire beginTransmission;
wire [23:0] colorData;
wire [23:0] colorDataSwapped;
wire [0:$clog2(PATTERNS_NUMBER * PATTERN_COLORS_NUMBER) - 1] colorIndexComputed;
wire irCommandReceived;
wire [31:0] irCommand;
wire rxFiltered;
initial begin
patternIndex = 0;
colorIndex = 0;
colorIndexShift = 0;
colorIndexShiftDirection = 0;
colorSwapIndex = 0;
unitCounter = 0;
txStart = 0;
pause = 0;
beginTransmissionDelay = 0;
end
assign colorIndexComputed = {patternIndex, (colorIndex + colorIndexShift)};
ROM1 rom(
.clock(clkIN),
.address(colorIndexComputed),
.q(colorData)
);
ColorSwap colorSwapper (
.dataIN(colorData),
.swapIN(colorSwapIndex),
.dataOUT(colorDataSwapped)
);
RXMajority3Filter rxInFilter (
.clockIN(clkIN),
.nResetIN(nResetIN),
.rxIN(rxIN),
.rxOUT(rxFiltered)
);
NecIrReceiver #(.CLOCK_SPEED(CLOCK_SPEED))
necIrReceiver (
.clkIN(clkIN),
.nResetIN(nResetIN),
.rxIN(~rxFiltered),
.dataReceivedOUT(irCommandReceived),
.dataOUT(irCommand)
);
ClockDivider #(.VALUE(CLOCK_SPEED / UPDATES_PER_SECOND))
beginTransmissionTrigger (
.clkIN(clkIN),
.nResetIN(nResetIN),
.clkOUT(beginTransmission)
);
WS2811Transmitter #(.CLOCK_SPEED(CLOCK_SPEED))
ws2811tx (
.clkIN(clkIN),
.nResetIN(nResetIN),
.startIN(txStart),
.dataIN(colorDataSwapped),
.busyOUT(ws2811Busy),
.txOUT(txOUT)
);
always @(posedge clkIN or negedge nResetIN) begin
if (!nResetIN) begin
patternIndex <= 0;
colorIndex <= 0;
colorIndexShift <= 0;
colorIndexShiftDirection <= 0;
colorSwapIndex <= 0;
unitCounter <= 0;
txStart <= 0;
pause <= 0;
beginTransmissionDelay <= 0;
end
else begin
if (irCommandReceived) begin
case (irCommand)
IR_COMMAND_PLAY : pause <= ~pause;
IR_COMMAND_EQ : colorIndexShiftDirection <= ~colorIndexShiftDirection;
IR_COMMAND_NEXT : patternIndex <= patternIndex + 1;
IR_COMMAND_PREV : patternIndex <= patternIndex - 1;
IR_COMMAND_PLUS : colorSwapIndex <= (colorSwapIndex == 3'd5) ? 0 : (colorSwapIndex + 1);
IR_COMMAND_MINS : colorSwapIndex <= (colorSwapIndex == 0) ? 3'd5 : (colorSwapIndex - 1);
endcase
end
if (beginTransmission) begin
unitCounter <= UNITS_NUMBER;
colorIndex <= 0;
case ({colorIndexShiftDirection, pause})
2'b10 : colorIndexShift <= colorIndexShift + 1;
2'b00 : colorIndexShift <= colorIndexShift - 1;
endcase
beginTransmissionDelay <= 1;
end
else if (beginTransmissionDelay) begin
beginTransmissionDelay <= 0;
end
else if (unitCounter != 0 && !ws2811Busy) begin
colorIndex <= colorIndex + 1;
unitCounter <= unitCounter - 1;
txStart <= 1;
end
else begin
txStart <= 0;
end
end
end
endmodule. “” beginTransmission , . irCommandReceived : , , RGB ColorSwap .
EP4CE6E22C8N , M9K Memory Blocks. , , ROM, 24- . .mif , ROM Megafunction Quartus ROM.v . .mif .sof , .
color_patterns_generator.js Node.js, rom.mif :
fs = require("fs");
const MODE_REPEAT = "repeat";
const MODE_STRETCH = "stretch";
const MODE_GRADIENT_STRETCH = "gradient-stretch";
const ROM_FILE_NAME = "rom.mif";
const COLORS_NUM = 128;
const COLORS_PATTERNS = [{
mode: MODE_GRADIENT_STRETCH,
colors: [
0xff0000,
0xff0000,
0xff00ff,
0xff00ff,
0x0000ff,
0x0000ff,
0xff00ff,
0xff00ff,
0xffff00,
0xffff00,
0x00ffff,
0x00ffff,
0x00ff00,
0x00ff00,
0xff0000,
]
}, {
mode: MODE_STRETCH,
colors: [
0xff0000,
0xff0000,
0xff00ff,
0xff00ff,
0x0000ff,
0x0000ff,
0xff00ff,
0xff00ff,
0xffff00,
0xffff00,
0x00ffff,
0x00ffff,
0x00ff00,
0x00ff00,
]
}, {
mode: MODE_REPEAT,
colors: [
0xff0000,
0xff0000,
0xff0000,
0xff0000,
0xff0000,
0xff0000,
0xff0000,
0xffffff,
0xff00ff,
0xff00ff,
0xff00ff,
0xff00ff,
0xff00ff,
0xff00ff,
0xff00ff,
0xffffff,
0x0000ff,
0x0000ff,
0x0000ff,
0x0000ff,
0x0000ff,
0x0000ff,
0x0000ff,
0xffffff,
0xff00ff,
0xff00ff,
0xff00ff,
0xff00ff,
0xff00ff,
0xff00ff,
0xff00ff,
0xffffff,
0xffff00,
0xffff00,
0xffff00,
0xffff00,
0xffff00,
0xffff00,
0xffff00,
0xffffff,
0x00ffff,
0x00ffff,
0x00ffff,
0x00ffff,
0x00ffff,
0x00ffff,
0x00ffff,
0xffffff,
0x00ff00,
0x00ff00,
0x00ff00,
0x00ff00,
0x00ff00,
0x00ff00,
0x00ff00,
0xffffff,
]
}, {
mode: MODE_REPEAT,
colors: [
0xff0000,
0xff0000,
0x00ff00,
0x00ff00,
0xffff00,
0xffff00,
0xff0000,
0xff0000,
0xff0000,
0x00ff00,
0x00ff00,
0x00ff00,
0xffff00,
0xffff00,
0xffff00,
0xff00ff,
0xff00ff,
0xff00ff,
0xff00ff,
0x00ff00,
0x00ff00,
0x00ff00,
0x00ff00,
0xffff00,
0xffff00,
0xffff00,
0xffff00,
]
}
];
function getRed(color) {
return ((color >> 16) & 0xff)
}
function getGreen(color) {
return ((color >> 8) & 0xff)
}
function getBlue(color) {
return ((color) & 0xff)
}
function toHex(d) {
let result = Number(d).toString(16).toUpperCase();
return result.length % 2 ? "0" + result : result;
}
function generate() {
let result = "";
let byteAddress = 0;
result += "WIDTH = 24; -- The size of data in bits\n";
result += "DEPTH = " + (COLORS_NUM * COLORS_PATTERNS.length) + "; -- The size of memory in words\n";
result += "ADDRESS_RADIX = HEX; -- The radix for address values\n";
result += "DATA_RADIX = HEX; -- The radix for data values\n";
result += "CONTENT -- start of (address : data pairs)\n";
result += "BEGIN\n";
let red;
let green;
let blue;
for (let pattern of COLORS_PATTERNS) {
for (let i = 0; i < COLORS_NUM; i++) {
if (pattern.mode === MODE_GRADIENT_STRETCH) {
let index = i * (pattern.colors.length - 1) / COLORS_NUM;
let colorA = pattern.colors[Math.floor(index)];
let colorB = pattern.colors[Math.floor(index) + 1];
let colorBValue = index % 1;
let colorAValue = 1 - colorBValue;
red = Math.round(getRed(colorA) * colorAValue + getRed(colorB) * colorBValue);
green = Math.round(getGreen(colorA) * colorAValue + getGreen(colorB) * colorBValue);
blue = Math.round(getBlue(colorA) * colorAValue + getBlue(colorB) * colorBValue);
} else if (pattern.mode === MODE_STRETCH) {
let index = Math.floor(i * pattern.colors.length / COLORS_NUM);
let color = pattern.colors[index];
red = getRed(color);
green = getGreen(color);
blue = getBlue(color);
} else if (pattern.mode === MODE_REPEAT) {
let index = i % pattern.colors.length;
let color = pattern.colors[index];
red = getRed(color);
green = getGreen(color);
blue = getBlue(color);
}
result +=
toHex(i + byteAddress) + " : " +
toHex(red) +
toHex(green) +
toHex(blue) + ";\n";
}
byteAddress += COLORS_NUM;
}
result += "END;";
return result;
}
try {
fs.writeFileSync(ROM_FILE_NAME, generate());
console.log("Success");
} catch (err) {
console.log("Failed\n", err);
}:
.