§ Принцип работы SPI
Этот протокол много где может использоваться, в разных модификациях, например, по SPI можно передавать не только по одному биту данных за 1 такт, а по 4 сразу, однако, передачу по 4 бита данных я использовать не буду, только 1 бит.Протокол предполагает что есть как минимум 3 провода:
- MOSI - входящие данные из ведущего контроллера (master)
- MISO - исходящие данные из ведомого контроллера (slave)
- SCLK - тактовая частота от ведущего контроллера
§ Порты
Любой модуль начинается с объявления портов ввода-вывода.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 тактов, старший бит становится на свое место.
Эффективная скорость модуля будет невысокой. Если он работает на частоте 25 мгц, то для того чтобы отослать 1 бит данных, требуется 4 такта, что дает 6.25 Мгц. Если же разделить на 8 бит (но так конечно, все равно не будет, так что +2 бита добавляем), то теоретически максимальная возможная скорость подобного устройства будет равна 625 килобайт в секунду. То есть это максимально простой модуль из возможных.
На этом все. Этот модуль не является конечным модулем в процессе взаимодействия с процессором, поскольку ему нужен еще один контроллер-прослойка для x86, но в целом, его можно использовать с другими процессорами, которые, например, пишут в порты напрямую.
[<< Предыдущая] [Оглавление]