fpga-systems-magazine

Сага о светодиодах. Часть 2. Разделяй и управляй

Главная » Статьи » Разное » Общее
sergebalakshiy
13.06.2022 14:43
2305
0
0.0

Оглавление

*О найденных опечатках и замечаниях просим сообщить admin@fpga-systems.ru

Разделяй и управляй

Представлена следующая часть из раздела “Сага о светодиодах”

Введение

Рассмотрен этап конструирования устройства управления (УУ), задачей которого является управление источником сигналов и передатчиком этих сигналов по протоколу I2C в приёмник сигналов для их исполнения. Взаимодействие модулей с УУ осуществляется по принципу ведомый-ведущий.

Использование протокола I2C

Немного о протоколе

I2C (Inter-Integrated Circuit) — двунаправленная шина, с последовательной передачей данных и возможностью адресации до 128 устройств. Физически шина состоит из двух сигнальных линий связи, одна (SDA) служит для обмена данными и другая (SCL) используется для передачи тактового сигнала. Дальнейшее описание шины и протокола обмена данными я опущу, так как в сети существует оригинальный документ описывающий спецификацию шины THE I2C-BUS SPECIFICATION VERSION 2.1 JANUARY 2000, а также русскоязычное описание, которое можно найти, например здесь или вот здесь и во многих других местах.

Техническое задание

  1. Схема должна управлять переключением от 3 до 10 линий светодиодов.
  2. Драйвер светодиодов должен быть отдельным устройством управляемым по двунаправленной шине 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)

Итоги

В этой главе было выполнено конструирование не сложного устройства управления.

  1. Был применён принцип разделения схемы на более простые модули и сборки из этих простых модулей более сложных конструкций.
  2. В управлении подчинёнными модулями был применён метод ведомый/ведущий. Это позволило упростить синхронизацию работы частей схемы и согласовать работу модулей между собой.
  3. При проектировании использовались различные инструменты, например, symbolator. Этот инструмент помогал еще на этапе проектирования создавать каркас модулей с отображением их портов. Это позволило спроектировать связи модулей еще до прогона всего проекта в Quartus.
  4. Выработал для себя правило, что все входные порты это как правило провода (wire), а выходные порты это как правило регистры (reg). Тогда модули легко соединять между собой обычными проводами, как это и делается на практике.

В следующей главе будет рассмотрено проектирование приемо-передающего модуля со стороны i2c_slave. И далее я хочу рассмотреть работу с линией связи по протоколу SPI. И далее, конечно, будет что-то далее...


Кроме того, я хотел бы увидеть конструктивные замечания в отношении данного проекта, так как мой статус в этой области "студент'' и мне еще многому предстоит научиться. Спасибо.

 

      Мотивировать автора     

     Поддержать FPGA комьюнити     

Оставить комментарий/отзыв

 

 

2305
0
0.0

Всего комментариев : 0
avatar

FPGA-Systems – это живое, постоянно обновляемое и растущее сообщество.
Хочешь быть в курсе всех новостей и актуальных событий в области?
Подпишись на рассылку

ePN