Работа FPGA с SPI периферией

On 04.11.2016 by nikellanjilo

В данной статье рассмотрен вопрос работы ПЛИС с периферийными устройствами, например с 8-канальным 12-битным АЦП ADC128S102 ( datasheet ). Использование этого устройства в любых проектах обусловленно простотой ( АЦП опрашивается по SPI интерфейсу и по нему же АЦП выдает данные с каждого из каналов) и малыми затратами программистов на работу с данным устройством.

В чем же заключается простота работы с SPI переферией?

Дело в том, что достаточно просто подать клок (тактовую частоту), изменить чипселект (CS), тем самым дать переферийному устройству понять, что с ним сейчас работают и подать данные.

На примере нашего ADC128S102 процесс работы происходит следующим образом…

adc128s102-time

В самом начале ПЛИС выставляет выходную ножку чипселекта (CS) в логическую 1, клок (SCLK) не подается [на самом деле не имеет значения — подается тактовая частота или нет — АЦП не реагирует на нее, т.к. CS свидетельствует о том, что с устройством сейчас не работают], данные по DOUT  не идут.

В нашей программе идет циклический опрос каналов АЦП с записью данных в память и последующей отправкой полученных данных по UART на ПК [об этом — в следующей статье].

module adc(
            input fab_clk_8MHz,      // Тактовая частота 8 МГц
            output reg SENSE_CS = 1, // Два ЦАП
            input SENSE_DIN,          // Данные, приходящие с АЦП
            output reg SENSE_DOUT,      // Данные с ПЛИС на АЦП (выбор канала)
            output sclk              // Тактовая частота для управления АЦП
            ); 

assign sclk = fab_clk_8MHz; 

// Переменные 
reg [1:0] state = 2'b00;
reg [7:0] data_2adc [8:0];
reg [3:0] cnt_frame= 0;
reg [9:0] cnt = 0;
reg [11:0] data_from_adc [8:0]; 
reg [11:0] data_adc_save [8:0];

// Начальная инициализация данных для отправки данных на АЦП (по документации - важны лишь 
// 6,5,4 биты. 
initial
begin
    data_2adc[0] = 8'b00_000_000; // none
    data_2adc[1] = 8'b11_000_111; // IN0
    data_2adc[2] = 8'b11_001_111; // IN1
    data_2adc[3] = 8'b11_010_111; // IN2
    data_2adc[4] = 8'b11_011_111; // IN3
    data_2adc[5] = 8'b11_100_111; // IN4
    data_2adc[6] = 8'b11_101_111; // IN5
    data_2adc[7] = 8'b11_110_111; // IN6
    data_2adc[8] = 8'b11_111_111; // IN7
end

always @ (negedge fab_clk_8MHz)
  begin
   case (state)
    2'b00 : begin
                state <= 2'b01;
                cnt_frame <= 0;
            end
    2'b01 : begin
             if (cnt_frame < 8)
                begin
                  cnt_frame <= cnt_frame + 1;
                  state <= 2'b10;
                  cnt <= 0;
                end
             else
                state <= 2'b00;
            end
     2'b10 : begin
            if (cnt <=50)
            begin
                cnt <= cnt + 1;
             if (cnt <= 15 )
                    SENSE_CS <= 0;
             if (cnt >15 && cnt <= 50)
                    SENSE_CS <= 1;
             end
             else
                 begin
                    state <= 2'b01;
                    cnt <= 0;
                  end
              end
     endcase
  end

// Отправка данных на АЦП
always @ (negedge fab_clk_8MHz)
begin
     if (cnt > 0 && cnt <= 7)
            SENSE_DOUT <= data_2adc[cnt_frame][7-cnt];
end

// Чтение и запись данных с АЦП
always @ (posedge fab_clk_8MHz)
begin
    if (cnt >= 5 && cnt <= 16)
            data_from_adc [cnt_frame][16-cnt] <= SENSE_DIN;
else 
    begin 
        if (cnt == 18) 
                data_adc_save[cnt_frame] <= data_from_adc[cnt_frame]; 
    end 
end            
endmodule 

Промоделировав код в ModelSim получим результат:
1_%d0%ba%d0%b0%d0%bd%d0%b0%d0%bb
Как видно из рисунка данные SENSE_DOUT отправляются согласно datasheet’у… На данном фрагменте
ПЛИС запрашивает данные канала IN0 (первый канал)…
В более развернутом масштабе получим следующую картинку:
%d0%b2%d1%81%d0%b5_%d0%ba%d0%b0%d0%bd%d0%b0%d0%bb%d1%8b
Между опросами я ввел некоторую задержку, ее можно отрегулировать в коде, меняя cnt (сейчас оно равно 50)
Файл с кодом можно скачать(.rar) -> adc

Добавить комментарий