你好。大约两年前,我在速卖通上购买了中文套件,其中包括EasyCycle A2.2调试板和板载Cyclone IV EP4CE6E22C8N,SE-020401红外遥控器,编程器,一对USB电缆和回路。很长一段时间,所有这些东西对我来说都是闲置的。我自己想不到任何有趣且不太费时的任务。
去年,我在同一速卖通上订购了基于著名的WS2811微电路的RGB LED灯带。在购买之前,在查看了YouTube对这些微电路特定协议的评论之后,我认为为FPGA编写自己的驱动器很有趣。由于 前面提到的板上有一个光电探测器,那么您还可以使用套件中的遥控器添加单击模式的功能。这样的新年前周末项目。
使用WS2811
实际上,从WS2811的数据表中可以明显看出,该协议非常简单:必须将24位RGB888 MSB优先格式的彩色数据传输到微电路的DIN输出。该微电路将在DOUT引脚上复制接下来的24位接收数据,这使得WS2811可以采用菊花链方式:
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
endmodule
562.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);
}
:
.