Сага о светодиодах. Часть 2. Разделяй и управляй¶
*О найденных опечатках и замечаниях просим сообщить
Разделяй и управляй¶
Представлена следующая часть из раздела “Сага о светодиодах”
Введение¶
Рассмотрен этап конструирования устройства управления (УУ), задачей которого является управление источником сигналов и передатчиком этих сигналов по протоколу I2C в приёмник сигналов для их исполнения. Взаимодействие модулей с УУ осуществляется по принципу ведомый-ведущий.
Использование протокола I2C¶
Немного о протоколе¶
I2C (Inter-Integrated Circuit) — двунаправленная шина, с последовательной передачей данных и возможностью адресации до 128 устройств. Физически шина состоит из двух сигнальных линий связи, одна (SDA) служит для обмена данными и другая (SCL) используется для передачи тактового сигнала. Дальнейшее описание шины и протокола обмена данными я опущу, так как в сети существует оригинальный документ описывающий спецификацию шины THE I2C-BUS SPECIFICATION VERSION 2.1 JANUARY 2000, а также русскоязычное описание, которое можно найти, например здесь или вот здесь и во многих других местах.
Техническое задание¶
- Схема должна управлять переключением от 3 до 10 линий светодиодов.
- Драйвер светодиодов должен быть отдельным устройством управляемым по двунаправленной шине I2C.
В предыдущей части мы рассматривали не сложную схему управления несколькими светодиодами. Преобразуем эту схему так, чтобы она соответствовала новому ТЗ разделив её на две части,

Рис. 1. Блок схема устройства с блоком I2C
Так что теперь все устройство будет состоять из нескольких частей;
- Устройство управления
- Драйвер светодиодов
- Устройство I2C
В дальнейшем займемся конструированием того, что на рисунке обозначено как устройство управления (УУ). Это устройство управления будет состоять из нескольких модулей:
- модуль
count- делитель частоты; - модуль
shift_cnt- счетчик номера линейки светодиодов; - модуль
i2c_master- ведущий приемо-передатчик.
На рисунке 2 представлено решение для левой части схемы.

Рис. 2. Блок схема устройства управления
Модуль I2C¶
Изобретать какой-то новый I2C модуль нет необходимости. На просторах интернета я нашел множество таких конструкций и с одной из них я и буду экспериментировать. Здесь приведена подробная информация о работе этого модуля. Ознакомившись с этой документацией можно приступить к конструированию устройства управления.
Модуль УУ¶
УУ должно управлять двумя устройствами;
- счетчиком линий светодиодов - модуль
cnt_dev, рисунок 3; - устройством I2C - модуль
i2c_master, рисунок 4.

Рис. 3. Блок схема счетчика линий светодиодов. Порты ввода/вывода

Рис. 4. Блок схема устройства I2C. Порты ввода/вывода
УУ должно уметь управлять соответствующими сигналами на линиях этих устройств. Для управления счетчиком необходимо добавить в УУ следующие линии:
busy_cnt- готовность счетчика к работе. Высокий уровень сигнала на линии говорит о том, что устройство занято;en_cnt- линия разрешения. Высокий уровень сигнала разрешает выполнить переключение счетчика;data_cnt- шина данных от счетчика линий светодиодов.
Для управления устройством I2C добавим следующие линии:
busy_i2c- готовность I2C к работе. Высокий уровень сигнала свидетельствует о готовности к обслуживанию;en_i2c- разрешение на работу. Высокий уровень разрешает выполнить обслуживание;miso- шина данных, полученных из линии;mosi- шина данных, подготовленных для передачи;rw- переключатель режима приема/передачи.
Также понадобятся общие сигналы управления:
CLK- линия тактового сигнала;RST- линия сигнала сброса;main_clk- линия основного синхронизирующего сигнала;rst_i2c- линия сигнала сброса для устройства I2C.
В результате получим общее устройство управления как на рисунке 5.

Рис. 5. Блок схема УУ. Порты ввода вывода
## Описание работы УУ\
Как отмечалось выше, сигнал o_busy - в состоянии 1 говорит о том, что I2C занят транзакцией, и надо ожидать, пока этот сигнал установится в 0, чтобы начать следующую транзакцию. В паре с этим сигналом, работает сигнал i_enable, который при активном высоком уровне разрешает начать новую транзакцию, если мастер не занят.
Для счетчика линий светодиодов Высокий уровень сигнала enable (EN) в модуле shift_cnt при низком уровне busy активирует этот модуль разрешая изменение состояния счетчика shift_cnt. Линия busy с высоким уровнем говорит о том, что устройство занято.
УУ должно учитывать состояние этих сигналов в следующих модулях и правильно их обрабатывать.
- модуль счетчика светодиодов (
cnt_dev); - модуль I2C.
Взаимодействие для обмена данными с этими модулями построим по схеме master-slave или ведомый-ведущий. Тогда общее устройство управления (УУ) будет ведущим, а счетчик светодиодов и I2C будут ведомыми, как на рисунке 6.

Рис. 6. Функциональная схема УУ по схеме ведомый-ведущий
Взаимодействие мастера с ведомым устройством будет осуществляться следующим образом.
- Мастер запрашивает сигнал
busyот подчиненного устройства, и как толькоbusyстанет низким, мастер устанавливает сигналenableв высокий уровень. - Мастер запрашивает и получает данные от подчиненного устройства, либо устанавливает данные для подчиненного устройства.
- Затем, мастер снова запрашивает сигнал
busyи как только он станет высоким, мастер устанавливает низкий уровень сигналаenable. -
И, когда
busyстанет снова низким, это будет свидетельствовать об окончании транзакции. Таким образом пара сигналовbusy/enableпроходит следующий цикл: -
10ведомое устройство готово к выполнению транзакции; 01ведомое устройство свободно, начало транзакции - получение либо передача данных;10освобождение ведомого устройства;00окончание транзакции; Таким образом устройство управления взаимодействует с устройством I2C. Аналогичным образом устройство управления будет взаимодействовать с УУС.
Конструкция модуля cnt_dev¶
Доработаем модуль счетчика так, чтобы модуль мог участвовать в цикле обмена данными с ведущим устройством. Для этого добавим в модуль shift_cnt сигнал busy и сигнал enable и обеспечим их работу в соответствии с выше указанными требованиями,
Листинг 2.1. Модуль счетчика линий светодиодов, доработанный
module shift_cnt #(parameter SIZE=4, LED_CNT=5)
(input wire CLK,
input wire RST,
input wire EN,
output reg [SIZE-1:0] CNT_N,
output reg busy);
localparam NULL = 'b0;
always @(posedge CLK, posedge EN, posedge RST) begin
if(RST==1)begin
CNT_N <= NULL;
busy <= 1;
end
else begin
if(EN==1)begin
busy = 1;
if(CNT_N<LED_CNT)begin
CNT_N <= CNT_N + 1'b1;
end
else begin
CNT_N <= NULL;
end
end
else if(EN == 0)begin
busy = 0;
end
end
end // always @ (posedge CLK_IN)
endmodule // shift_cnt
enable. Соберем счетчик и генератор в один модуль cnt_dev.
Листинг 2.2. Сборочный модуль счетчика светодиодов
module cnt_dev #(parameter SIZE=8,
CLK_DIV=25'd64,
LED_NUM=5)
(
output wire OCLK,
input CLK,
input RST,
input wire EN,
output busy,
output [SIZE-1:0] CNT_N
);
//--------------------------------
localparam LED_CNT=LED_NUM-1;
//--------------------------------
wire strb;
//--------------------------------
assign OCLK = CLK;
//Настраиваем модуль count
count #(.TIME1(CLK_DIV))
i_count(
.CLK(CLK),
.RST(RST),
.STB(strb));
//Присоединим модуль shift_cnt
shift_cnt #(.LED_CNT(LED_CNT),
.SIZE(SIZE))
i_shift_cnt (
.CLK(strb),
.RST(RST),
.CNT_N(CNT_N),
.busy(busy),
.EN(EN));
endmodule // ctl_dev
Конструкция модуля УУ¶
Так как в модуле i2c_master уже есть все необходимые сигналы, то можно приступить к конструированию модуля УУ (ctl_dev). Блок схема представлена на рисунке 7.

Рис. 7. Блок схема УУ со счетчиком и приёмо-передатчиком.
Этот модуль предназначен для управления модулями cnt_dev и i2c_master. Он должен иметь необходимые для этой задачи входные и выходные сигналы. Следующий листинг описывает спецификацию портов ввода вывода.
Листинг 2.3. Часть модуля УУ. Порты ввода вывода
`timescale 1ns / 1ps
module ctl_dev #(parameter SIZE=8,
CLK_DIV=25<d64,
LED_NUM=5)
(
input wire CLK,
input wire RST,
input wire main_clk,
input wire busy_cnt,
input wire [SIZE-1:0] data_cnt,
input wire busy_i2c,
input wire [SIZE-1:0] miso,
output reg en_cnt,
output reg [SIZE-1:0] mosi,
output reg en_i2c,
output reg rw,
output reg rst_i2c
);
Работа блока always, цикл передатчикa¶
Основная задача УУ состоит в том, чтобы получить байт данных от счетчика линий светодиодов с номером линии и отдать этот байт в устройство I2C для последующей передачи в драйвер светодиода. Это показано в следующем листинге.
Листинг 2.4. Часть модуля УУ. Шаг 2:
2: begin
if (busy_cnt == 0) begin
rw <= 0; //write operation
en_cnt <= 1;
mosi <= data_cnt;
proc_cntr <= proc_cntr + 1;
end
else begin
en_cnt <= 0;
end
end
busy_cnt == 0), то включаем режим передачи, запрашиваем номер линии светодиодов, и отдаем его на передачу.
Листинг 2.5. Часть модуля УУ. Шаг 3:
3: begin
//if master is not busy set enable high
if(busy_i2c == 0)begin
en_i2c <= 1;
proc_cntr <= proc_cntr + 1;
end
end
i2c_mester не занят (busy_i2c == 0), то активируем передачу сигналом en_i2c переводя его на высокий уровень.
Листинг 2.6. Часть модуля УУ. Шаг 4:
4: begin
//once busy set enable low
if(busy_i2c == 1)begin
en_i2c <= 0;
proc_cntr <= proc_cntr + 1;
end
end
i2c_master переводя en_i2c в низкий уровень.
Листинг 2.7. Часть модуля УУ. Шаг 5:
5: begin
// as soon as busy is low again an operation has been
// completed
if(busy_i2c == 0) begin
proc_cntr <= proc_cntr + 1;
end
end
i2c_master (низкий уровень busy_i2c). Это свидетельствует об окончании цикла передачи. После этого можем начать цикл приема.
Работа блока always, цикл приёма¶
Листинг 2.8. Часть модуля УУ. Цикл приема данных
20: begin
rw <= 1; //write operation
mosi <= data_cnt;
proc_cntr <= proc_cntr + 1;
end
21: begin
if(busy_i2c == 0)begin
en_i2c <= 1;
$display("Enabled read");
$display("принимаем", " mosi=", mosi, " miso=", miso);
proc_cntr <= proc_cntr + 1;
end
end
22: begin
if(busy_i2c == 1)begin
en_i2c <= 0;
proc_cntr <= proc_cntr + 1;
end
end
23: begin
if(busy_i2c == 0)begin
read_data <= miso;
proc_cntr <= proc_cntr + 1;
$display("Master done reading");
end
end
Работа блока always, обработка ошибок¶
Листинг 2.9. Часть модуля УУ. Обработка ошибок
24: begin
if(read_data == mosi) begin //data_to_write)begin
$display("Read back correct data!",
" mosi=", mosi, " miso=", miso);
end
else begin
$display("Read back incorrect data!");
end
//$stop;
proc_cntr <= 0;
end
Полная сборка устройства¶
Соберём всё устройства в один модуль top_dev, листинг 10.
Листинг 2.10. Модуль верхнего уровня
`timescale 1ns / 1ps
module top_dev #(parameter SIZE=8)
(input main_clk,
input RST,
inout wire sda,
inout wire scl
);
// ctl_dev ----------------
wire [SIZE-1:0] data_cnt_w;
wire en_cnt;
wire busy_cnt;
// i2c_dev ----------------
wire busy_i2c;
wire en_i2c;
wire [SIZE-1:0] mosi;
wire [SIZE-1:0] miso;
wire rw;
wire rst_i2c;
//-------------------------
ctl_dev i_ctl_dev (.main_clk(main_clk),
.RST(RST),
.en_cnt(en_cnt),
.busy_cnt(busy_cnt),
.data_cnt(data_cnt_w),
.busy_i2c(busy_i2c),
.en_i2c(en_i2c),
.mosi(mosi),
.miso(miso),
.rst_i2c(rst_i2c),
.rw(rw)
);
cnt_dev i_cnt_dev(.CLK(main_clk),
.RST(RST),
.EN(en_cnt),
.busy(busy_cnt),
.CNT_N(data_cnt_w)
);
reg [SIZE-1:0] reg_addr =0;
reg [SIZE-2:0] dev_addr =7'b001_0001;
reg [15:0] divider = 16'h0004;
i2c_master #(.DATA_WIDTH(8),.REG_WIDTH(8),.ADDR_WIDTH(7))
i_i2c_master(
.i_clk(main_clk),
.i_rst(rst_i2c),
.o_busy(busy_i2c),
.i_enable(en_i2c),
.i_mosi_data(mosi),
.o_miso_data(miso),
.i_rw(rw),
.i_reg_addr(reg_addr),
.i_device_addr(dev_addr),
.i_divider(divider),
.io_sda(sda),
.io_scl(scl)
);
endmodule // top_dev
Испытательный стенд¶
Для испытаний подключим модуль top_dev и к нему модуль i2c_slave. Модуль i2c_slave нужен для осуществления полного цикла приема передачи данных. Также он имитирует драйвер светодиодов.
Листинг 2.11. Испытательнвый стенд
`timescale 1ns / 1ps
module top_dev_tb();
localparam DEPTH = 256;
localparam SIZE = $clog2(DEPTH);
localparam LED_NUM = 5;
localparam LED_CNT = LED_NUM-1;
real clockDelay50 = ((1/ (50e6))/2)*(1e9);
reg main_clk = 0;
reg rst = 1;
reg CLK = 0;
reg rst_i2c=1;
always begin
#clockDelay50;
main_clk = ~main_clk;
end
reg RST;
reg en_cnt;
reg [4:0] cnt_repeat;
wire scl;
wire sda;
pullup p1(scl); // pullup scl line
pullup p2(sda); // pullup sda line
top_dev i_top_dev(.main_clk(main_clk),
.RST(RST),
.scl(scl),
.sda(sda));
i2c_slave i2c_slave_model_inst(.scl(scl),
.sda(sda));
initial begin
main_clk = 0;
RST = 0;
cnt_repeat = 0;
#100 RST = 1;
#25 RST = 0;
#100;
@(posedge main_clk)
#0;
begin
repeat (4) begin
cnt_repeat <= cnt_repeat + 1;
$monitor("TB:", cnt_repeat, " en_cnt=",
i_top_dev.i_ctl_dev.en_cnt, " mosi=",
i_top_dev.i_ctl_dev.mosi);
end
end
end // initial begin
initial begin
#320000 $finish;
end
initial begin
$dumpfile("out.vcd");
$dumpvars(0,top_dev_tb);
end
initial
$monitor(CLK, RST);
endmodule // ctl_i2c_dev_tb
repeat(4) задаем 4 полных цикла приёма передачи. И, при помощи оператора $display отслеживаем отправленные и принятые данные. Запустим стенд. Получается вот такая картинка.
...
Read back correct data! mosi= 0 miso= 0
Read back correct data! mosi= 1 miso= 1
Read back correct data! mosi= 2 miso= 2
Read back correct data! mosi= 3 miso= 3
Read back correct data! mosi= 4 miso= 4
...
top_dev_tb.v:56: $finish called at 600000000 (1ps)
Итоги¶
В этой главе было выполнено конструирование не сложного устройства управления.
- Был применён принцип разделения схемы на более простые модули и сборки из этих простых модулей более сложных конструкций.
- В управлении подчинёнными модулями был применён метод ведомый/ведущий. Это позволило упростить синхронизацию работы частей схемы и согласовать работу модулей между собой.
- При проектировании использовались различные инструменты, например, symbolator. Этот инструмент помогал еще на этапе проектирования создавать каркас модулей с отображением их портов. Это позволило спроектировать связи модулей еще до прогона всего проекта в Quartus.
- Выработал для себя правило, что все входные порты это как правило провода (
wire), а выходные порты это как правило регистры (reg). Тогда модули легко соединять между собой обычными проводами, как это и делается на практике. В следующей главе будет рассмотрено проектирование приемо-передающего модуля со стороныi2c_slave. И далее я хочу рассмотреть работу с линией связи по протоколу SPI. И далее, конечно, будет что-то далее...
Кроме того, я хотел бы увидеть конструктивные замечания в отношении данного проекта, так как мой статус в этой области "студент" и мне еще многому предстоит научиться. Спасибо.