Nous allumons la bande LED basée sur WS2811 en utilisant FPGA



Bonjour. Il y a prÚs de deux ans, j'ai acheté un kit chinois sur aliexpress, composé d'une carte de débogage EasyFPGA A2.2 avec Cyclone IV EP4CE6E22C8N à bord, d'une télécommande infrarouge SE-020401, d'un programmateur, d'une paire de cùbles USB et de boucles. Pendant longtemps, tout ce truc est resté inactif avec moi, tk. Je ne pouvais penser à aucune tùche intéressante et pas trop longue pour moi.



L'annĂ©e derniĂšre, sur le mĂȘme aliexpress, j'ai commandĂ© une bande LED RVB basĂ©e sur les microcircuits WS2811 bien connus. Avant d'acheter, aprĂšs avoir regardĂ© la revue YouTube du protocole spĂ©cifique de ces microcircuits, j'ai dĂ©cidĂ© qu'il serait intĂ©ressant d'Ă©crire mon propre pilote pour eux pour les FPGA. Et depuis la carte susmentionnĂ©e a un photodĂ©tecteur Ă  bord, vous pouvez Ă©galement ajouter la possibilitĂ© de cliquer sur les modes avec la tĂ©lĂ©commande du kit. Un tel projet de week-end avant le Nouvel An.



Travailler avec WS2811



En fait, d'aprĂšs la fiche technique du WS2811, il est clair que le protocole est assez simple: 24 bits de donnĂ©es de couleur au format RGB888 MSB-first doivent ĂȘtre transmis Ă  la sortie DIN du microcircuit. Le microcircuit dupliquera les 24 bits suivants de donnĂ©es reçues sur la broche DOUT, ce qui permet au WS2811 d'ĂȘtre connectĂ© en sĂ©rie:



Schéma de connexion série pour puces 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.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



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