§ Принцип работы SPI
Этот протокол много где может использоваться, в разных модификациях, например, по SPI можно передавать не только по одному биту данных за 1 такт, а по 4 сразу, однако, передачу по 4 бита данных я использовать не буду, только 1 бит.Протокол предполагает что есть как минимум 3 провода:
- MOSI - входящие данные из ведущего контроллера (master)
- MISO - исходящие данные из ведомого контроллера (slave)
- SCLK - тактовая частота от ведущего контроллера
§ Порты
Любой модуль начинается с объявления портов ввода-вывода.module sd ( // 25 Mhz input clock, input reset_n, // Интерфейс с SPI output reg SPI_CS, output reg SPI_SCLK, input SPI_MISO, output reg SPI_MOSI, // Интерфейс input sd_signal, // =1 Прием команды input [ 1:0] sd_cmd, // ID команды input [ 7:0] sd_out, // Входящие данные из процессора output reg [ 7:0] sd_din, // Исходящие данные в процессор output reg sd_busy, // =1 Устройство занято output sd_timeout // =1 Вышел таймаут ); endmoduleМодуль тактируется 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)
assign sd_timeout = (sd_timeout_cnt == `SPI_TIMEOUT_CNT);Необходим объявить используемые в модуле регистры.
reg [24:0] sd_timeout_cnt = `SPI_TIMEOUT_CNT; reg [2:0] spi_process = 0; reg [3:0] spi_cycle = 0; reg [7:0] spi_data_w = 0; reg [7:0] spi_counter = 0; reg [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 кГц
Напишем основной обработчик. Назначим определения начальных значений портов.always @(posedge clock) if (reset_n == 1'b0) begin spi_process <= 0; sd_timeout_cnt <= `SPI_TIMEOUT_CNT; SPI_CS <= 1'b1; SPI_SCLK <= 1'b0; SPI_MOSI <= 1'b0; sd_din <= 8'h00; sd_busy <= 1'b0; end else begin if (sd_timeout_cnt < `SPI_TIMEOUT_CNT && spi_process == 0) sd_timeout_cnt <= sd_timeout_cnt + 1; endЭтот счетчик увеличивает sd_timeout_cnt на каждом такте на +1, но только тогда, когда функция находится в состоянии IDLE (spi_process=0)
Опишем основное состояние IDLE:
case (spi_process) // Ожидание команды 0: if (sd_signal) begin spi_process <= 1 + sd_cmd; spi_counter <= 0; spi_cycle <= 0; spi_data_w <= sd_out; sd_busy <= 1; sd_timeout_cnt <= 0; SPI_SCLK <= 0; end endcaseВ состоянии 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):
3, 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) уже немного посложнее:
2: begin SPI_CS <= 1; SPI_MOSI <= 1; // 125*100`000 if (spi_slow_tick == (125 - 1)) begin SPI_SCLK <= ~SPI_SCLK; spi_slow_tick <= 0; spi_counter <= spi_counter + 1; // 80 ticks if (spi_counter == (2*80 - 1)) begin SPI_SCLK <= 0; spi_process <= 0; sd_busy <= 0; end end // Отщелкивание таймера else begin spi_slow_tick <= spi_slow_tick + 1; end endАлгоритм работы будет такой. Устанавливаются стабильные сигналы 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.1: case (spi_cycle) // CLK-DN 0: begin SPI_SCLK <= 0; spi_cycle <= 1; SPI_MOSI <= spi_data_w[7]; spi_data_w <= {spi_data_w[6:0], 1'b0}; end 1: begin spi_cycle <= 2; SPI_SCLK <= 1; end // CLK-UP 2: begin spi_cycle <= 3; sd_din <= {sd_din[6:0], SPI_MISO}; end 3: begin SPI_SCLK <= 0; spi_cycle <= 0; spi_counter <= spi_counter + 1; if (spi_counter == 7) begin SPI_MOSI <= 0; sd_busy <= 0; spi_counter <= 0; spi_process <= 0; end end endcaseФункция состоит из двух 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, но в целом, его можно использовать с другими процессорами, которые, например, пишут в порты напрямую.
[<< Предыдущая] [Оглавление]