Kami menyalakan strip LED berdasarkan WS2811 menggunakan FPGA



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



WS2811Transmitter.sv
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



NecIrReceiver.sv
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.000281253556

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



Main.sv
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 :



color_patterns_generator.js
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);
}


:





.



GitHub




All Articles