§ Принцип работы SPI

Этот протокол много где может использоваться, в разных модификациях, например, по SPI можно передавать не только по одному биту данных за 1 такт, а по 4 сразу, однако, передачу по 4 бита данных я использовать не буду, только 1 бит.
Протокол предполагает что есть как минимум 3 провода:
  • MOSI - входящие данные из ведущего контроллера (master)
  • MISO - исходящие данные из ведомого контроллера (slave)
  • SCLK - тактовая частота от ведущего контроллера
Принцип действия довольно простой. Ведущий контроллер устанавливает SCLK в 0 и 1, при этом, когда SCLK установлен в 0, ставится сначала старший бит передаваемого байта, после чего меняется на 1, устройство принимает данные и генерирует старший бит ответа, то есть, когда идет передача данных на устройство, само устройство тоже генерирует ответ на каждом SCLK=1.

§ Порты

Любой модуль начинается с объявления портов ввода-вывода.
1module sd
2(
3    // 25 Mhz
4    input               clock,
5    input               reset_n,
6
7    // Интерфейс с SPI
8    output reg          SPI_CS,
9    output reg          SPI_SCLK,
10    input               SPI_MISO,
11    output reg          SPI_MOSI,
12
13    // Интерфейс
14    input               sd_signal,  // =1 Прием команды
15    input       [ 1:0]  sd_cmd,     // ID команды
16    input       [ 7:0]  sd_out,     // Входящие данные из процессора
17    output reg  [ 7:0]  sd_din,     // Исходящие данные в процессор
18    output reg          sd_busy,    // =1 Устройство занято
19    output              sd_timeout  // =1 Вышел таймаут
20);
21
22endmodule
Модуль тактируется 25 мгц генератором. На входе в модуль приходят данные от SPI_MISO, исходят SPI_MOSI, SPI_SCLK и SPI_CS. Сигнал CS (Chip Enabled), если установлен в 1, то ведомое устройство считается неактивным и он не должен принимать сигналы SPI_MOSI и SPI_SCLK. При CS=0, устройство (slave) активируется.
Теперь опишу порты той части, которая управляется микроконтроллером хоста.
  • sd_signal — при наличии сигнала =1, модуль начинает выполнение одну из 4-х функции, заданной в sd_cmd
  • sd_din — данные, которые принимаются от ведомого устройства после чтения байта
  • sd_out — данные, которые отсылаются от хост-контроллера к ведомому контроллеру
  • sd_busy — при 1, устройство занято выполнением команды
  • sd_timeout — сигнал 1 говорит о том, что модуль слишком долго не отсылал команды, в данном случае, 0.1 секунды (параметр SPI_TIMEOUT_CNT)
О том, занято ли устройство, делаем вывод по такому сравнению:
1assign sd_timeout = (sd_timeout_cnt == `SPI_TIMEOUT_CNT);
Необходим объявить используемые в модуле регистры.
1reg  [24:0] sd_timeout_cnt = `SPI_TIMEOUT_CNT;
2reg  [2:0]  spi_process = 0;
3reg  [3:0]  spi_cycle   = 0;
4reg  [7:0]  spi_data_w  = 0;
5reg  [7:0]  spi_counter   = 0;
6reg  [7:0]  spi_slow_tick = 0;
Объяснение назначения регистров.
  • sd_timeout_cnt — счетчик таймаута, изначально устанавливается в максимальный, чтобы таймаут уже был определен как =1
  • spi_process — номер функции, которая в данный момент исполняется (от 1 до 4), 0 — IDLE, устройство находится в режиме ожидания
  • spi_cycle — подфункция, или текущая строка исполнения конкретной функции
  • spi_data_w — временный сдвиговый регистр, откуда шлются данные на ведомое устройство
  • spi_counter — счетчик от 0 до 7, для приема и отсылки битов
  • spi_slow_tick — счетчик для замедления тактов до 10 кГц

§ Функции включения, выключения, IDLE и 10 кГц

Напишем основной обработчик. Назначим определения начальных значений портов.
1always @(posedge clock)
2if (reset_n == 1'b0) begin
3
4    spi_process     <= 0;
5    sd_timeout_cnt  <= `SPI_TIMEOUT_CNT;
6    SPI_CS          <= 1'b1;
7    SPI_SCLK        <= 1'b0;
8    SPI_MOSI        <= 1'b0;
9    sd_din          <= 8'h00;
10    sd_busy         <= 1'b0;
11
12end else begin
13
14    if (sd_timeout_cnt < `SPI_TIMEOUT_CNT && spi_process == 0)
15        sd_timeout_cnt <= sd_timeout_cnt + 1;
16
17end
Этот счетчик увеличивает sd_timeout_cnt на каждом такте на +1, но только тогда, когда функция находится в состоянии IDLE (spi_process=0)
Опишем основное состояние IDLE:
1case (spi_process)
2
3    // Ожидание команды
4    0: if (sd_signal) begin
5
6        spi_process     <= 1 + sd_cmd;
7        spi_counter     <= 0;
8        spi_cycle       <= 0;
9        spi_data_w      <= sd_out;
10        sd_busy         <= 1;
11        sd_timeout_cnt  <= 0;
12        SPI_SCLK        <= 0;
13
14    end
15
16endcase
В состоянии IDLE модуль будет находится до тех пор, пока не придет sd_signal=1. При появлении этого сигнала, это означает, что ранее sd_cmd уже был установлен правильно. Когда это происходит, то все счетчики сбрасываются, в том числе и sd_timeout_cnt, а модулю устанавливается состояние sd_busy=1. В регистр spi_data_w переписывается текущее состояние sd_out, защелкивается и теперь даже если sd_out как-то внешне поменяется, это никак не повлияет на передачу данных.
Рассмотрю две команды, sd_cmd=2 (Chip Enabled=0) и sd_cmd=3 (Chip Enabled=1):
13, 4: begin SPI_CS <= ~spi_process[0]; spi_process <= 0; sd_busy <= 0; end
При sd_cmd=2, в SPI_CS попадет бит 0 (включено), при sd_cmd=3 будет бит 1 (отключено). Модулю установится состояние busy=0 и перейдет в состояние IDLE (spi_process=0).
Команда sd_cmd=1 (10KHZ TICK) уже немного посложнее:
12: begin
2
3    SPI_CS   <= 1;
4    SPI_MOSI <= 1;
5
6    // 125*100`000
7    if (spi_slow_tick == (125 - 1)) begin
8
9        SPI_SCLK      <= ~SPI_SCLK;
10        spi_slow_tick <= 0;
11        spi_counter   <= spi_counter + 1;
12
13        // 80 ticks
14        if (spi_counter == (2*80 - 1)) begin
15
16            SPI_SCLK    <= 0;
17            spi_process <= 0;
18            sd_busy     <= 0;
19
20        end
21
22    end
23    // Отщелкивание таймера
24    else begin spi_slow_tick <= spi_slow_tick + 1;  end
25
26end
Алгоритм работы будет такой. Устанавливаются стабильные сигналы 1 на CS и MOSI, и через каждые 125 тактов будет перещелкивать SPI_SCLK из одного состояния в другое. Поскольку скорость тактового генератора равна 2500000 Гц, то, учитывая, что за один герц можно считать два изменения состояния за 1 секунду, то тогда рассчитываем, сколько будет герц выдавать SPI_SCLK по итогу, разделив 2500000 на 125 и еще разделив на 2, что получается 10 кГц.
Для чего это все нужно? Дело в том, что при инициализации sd-карты в режим SPI, необходимо подать 80 тактов на частоте 10 кГц, потому тут есть два счетчика, тот, который снижает такты с 25 мгц до 10 кГц и тот, который отсчитывает 80 таких тактов spi_counter == (2*80 - 1).
Как только это произошло, модуль устанавливает SPI_SCLK в 0 для будущей передачи данных.

§ Прием и передача данных

И осталось разобрать последнюю функцию, которая принимает и передает данные. Эта функция sd_cmd=1.
11: case (spi_cycle)
2
3    // CLK-DN
4    0: begin
5
6        SPI_SCLK    <= 0;
7        spi_cycle   <= 1;
8        SPI_MOSI    <= spi_data_w[7];
9        spi_data_w  <= {spi_data_w[6:0], 1'b0};
10
11    end
12    1: begin spi_cycle <= 2; SPI_SCLK <= 1; end
13    // CLK-UP
14    2: begin spi_cycle <= 3; sd_din <= {sd_din[6:0], SPI_MISO}; end
15    3: begin
16
17        SPI_SCLK    <= 0;
18        spi_cycle   <= 0;
19        spi_counter <= spi_counter + 1;
20
21        if (spi_counter == 7) begin
22
23            SPI_MOSI    <= 0;
24            sd_busy     <= 0;
25            spi_counter <= 0;
26            spi_process <= 0;
27
28        end
29    end
30
31endcase
Функция состоит из двух 4 фаз.
  • spi_cycle=0, SPI_SCLK устанавливается в 0, и на SPI_MOSI устанавливает старший бит передаваемого байта от хост-контроллера к ведомому контроллеру, и тут же смещается влево
  • spi_cycle=1, SPI_SCLK в 1
  • spi_cycle=2, из SPI_MISO попадает старший бит данных, которые пришли от устройства
  • spi_cycle=3, регистр sd_din сдвигается влево, то есть, сначала в младший его бит попадает старший, и через 8 тактов, старший бит становится на свое место.
Когда было принято 8 бит (spi_counter == 7), то процесс переходит в IDLE и на SPI_MOSI тоже ставится 0 в исходное состояние. Занятость модуля убирается, sd_busy=0, и все счетчики очищаются перед новой итерацией.
Эффективная скорость модуля будет невысокой. Если он работает на частоте 25 мгц, то для того чтобы отослать 1 бит данных, требуется 4 такта, что дает 6.25 Мгц. Если же разделить на 8 бит (но так конечно, все равно не будет, так что +2 бита добавляем), то теоретически максимальная возможная скорость подобного устройства будет равна 625 килобайт в секунду. То есть это максимально простой модуль из возможных.
На этом все. Этот модуль не является конечным модулем в процессе взаимодействия с процессором, поскольку ему нужен еще один контроллер-прослойка для x86, но в целом, его можно использовать с другими процессорами, которые, например, пишут в порты напрямую.