[FPGA] Зажигаем светодиодную ленту на базе WS2811 при помощи ПЛИС
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Всем привет. Уже почти два года назад я приобрел на aliexpress китайский набор, состоящий из отладочной платы EasyFPGA A2.2, с Cyclone IV EP4CE6E22C8N на борту, ИК пульта SE-020401, программатора, пары USB проводов и шлейфов. Долгое время все это добро лежало у меня без дела, т.к. я никак не мог придумать для себя какой-то интересной и не слишком затратной по времени задачи.
Еще в прошлом году на том-же aliexpress я заказал RGB светодиодную ленту на базе всем известных WS2811 микросхем. Перед покупкой, посмотрев обзор в YouTube на специфический протокол этих микросхем, я решил, что будет интересно написать свой драйвер для них под ПЛИС. А т.к. вышеупомянутая плата на борту имеет фотоприемник, то еще и добавить возможность пощелкать режимы пультиком из комплекта. Такой себе предновогодний проект выходного дня.
Работа с WS2811
По сути, из даташита на WS2811 видно — что протокол довольно прост: на вывод DIN микросхемы нужно передать 24 бита данных цвета в формате RGB888 MSB-first. Следующие 24 бита принятых данных микросхема продублирует на выводе DOUT, что позволяет объединять 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
WS2811Transmitter.sv
SPL
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.
Единственная особенность данного модуля — это то что данные передаются по заднему фронту тактового импульса. Это было сделано для того, чтобы управляющая логика уже на следующий такт после инициации передачи данных реагировала на изменение сигнала 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
SPL
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
SPL
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
SPL
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
===========
Источник:
habr.com
===========
Похожие новости:
- [FPGA, Matlab] Реализация нейросетей на ПЛИС
- [FPGA, Настройка Linux, Разработка на Raspberry Pi] Второй HDMI монитор к Raspberry Pi3 через DPI интерфейс и FPGA плату
- [Компиляторы, FPGA, Компьютерное железо, Процессоры] Запуск Unix-подобной ОС на самодельном CPU с помощью самодельного компилятора C (перевод)
- [Бизнес-модели, Компьютерное железо, Производство и разработка электроники, Процессоры] AMD ведет переговоры о покупке Xilinx, чтобы выйти на рынок ПЛИС
- [Системное программирование, Программирование микроконтроллеров, Компьютерное железо] Моделирование прошивки в среде ModelSim с использованием моделей на языке SystemC
- [FPGA, Анализ и проектирование систем, История IT, Старое железо, Электроника для начинающих] Обратная разработка XC2064 — первой микросхемы FPGA (перевод)
- [FPGA, Высокая производительность, Искусственный интеллект, Конференции, Программирование микроконтроллеров] Бывший вице-президент Sun, MIPS и DEC прокомметировал покупку компании ARM компанией NVidia
- [FPGA, Программирование микроконтроллеров, Конференции, Компьютерное железо, Электроника для начинающих] Молодожены, которых Интел привез в Калифорнию, дали интервью в лабнике от ВШЭ МИЭМ. Как повторить их достижение
- [Исследования и прогнозы в IT, Производство и разработка электроники, Электроника для начинающих] Исследование рынка разработки электроники за 2019 год
- [FPGA, Java, Интерфейсы] Организовываем взаимодействие между ПК и ЦАП/АЦП при помощи ПЛИС
Теги для поиска: #_fpga, #_fpga, #_cyclone_iv, #_verilog, #_system_verilog, #_ws2811, #_nec_infrared_transmission_protocol, #_fpga
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 12:02
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Всем привет. Уже почти два года назад я приобрел на aliexpress китайский набор, состоящий из отладочной платы EasyFPGA A2.2, с Cyclone IV EP4CE6E22C8N на борту, ИК пульта SE-020401, программатора, пары USB проводов и шлейфов. Долгое время все это добро лежало у меня без дела, т.к. я никак не мог придумать для себя какой-то интересной и не слишком затратной по времени задачи. Еще в прошлом году на том-же aliexpress я заказал RGB светодиодную ленту на базе всем известных WS2811 микросхем. Перед покупкой, посмотрев обзор в YouTube на специфический протокол этих микросхем, я решил, что будет интересно написать свой драйвер для них под ПЛИС. А т.к. вышеупомянутая плата на борту имеет фотоприемник, то еще и добавить возможность пощелкать режимы пультиком из комплекта. Такой себе предновогодний проект выходного дня. Работа с WS2811 По сути, из даташита на WS2811 видно — что протокол довольно прост: на вывод DIN микросхемы нужно передать 24 бита данных цвета в формате RGB888 MSB-first. Следующие 24 бита принятых данных микросхема продублирует на выводе DOUT, что позволяет объединять 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 WS2811Transmitter.svSPLmodule 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. Единственная особенность данного модуля — это то что данные передаются по заднему фронту тактового импульса. Это было сделано для того, чтобы управляющая логика уже на следующий такт после инициации передачи данных реагировала на изменение сигнала 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.svSPLmodule 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.svSPLmodule 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.jsSPLfs = 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 =========== Источник: habr.com =========== Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 12:02
Часовой пояс: UTC + 5