Сводка
SPI (Serial Peripheral Interface) – это стандартный протокол связи, широко применяемый в электронике для обмена данными между микроконтроллерами и периферийными устройствами. В данной статье мы подробно рассмотрим, что такое SPI, как он работает и для чего его применяют.
SPI имеет ряд преимуществ, таких как высокая скорость передачи данных, простота реализации, поддержка различных режимов работы (полудуплексный, полный дуплекс) и возможность подключения нескольких ведомых устройств к одному мастеру. Благодаря этим характеристикам, SPI активно применяется в различных устройствах, таких как микроконтроллеры, датчики, дисплеи, память и другие периферийные устройства.
Принцип работы
SPI – это последовательный интерфейс, который обеспечивает передачу данных между одним мастером и одним или более ведомыми устройствами. Он базируется на принципах синхронной связи и может работать на различных стандартных и пользовательских частотах. SPI использует несколько сигнальных линий, такие как линия передачи данных (MOSI), линия приема данных (MISO), линия синхронизации (SCLK) и линия выбора устройства (SS), для обмена информацией.

Самая популярная схема подключения ведомых устройств показана на рисунке 2.

Управление параметрами CPOL (Clock Polarity) и CPHA (Clock Phase) в протоколе SPI является важным аспектом при взаимодействии между мастером и слейвом. Параметр CPOL определяет уровень активного состояния тактового сигнала (скажем, 0 или 1), а параметр CPHA определяет фазу захвата данных относительно тактового сигнала. Когда используются оба параметра вместе, возникает четыре комбинации, которые могут определять правила передачи данных. Настройка CPOL и CPHA имеет решающее значение для правильного функционирования оборудования, поэтому важно правильно выбирать их значения в зависимости от особенностей устройств в сети. Оптимально подобранные параметры могут значительно повысить эффективность передачи данных и надежность работы системы в целом.
Достоинства и Недостатки
Главными достоинствами SPI являются высокая скорость передачи данных, простота реализации и надежность работы. SPI позволяет передавать данные синхронно, что обеспечивает точную синхронизацию между устройствами. Кроме того, данный интерфейс поддерживает подключение нескольких устройств к одному микроконтроллеру, что позволяет значительно упростить систему связи.
Однако у SPI есть и недостатки. Один из них — ограниченное количество линий передачи данных, что может ограничить количество подключаемых устройств. Кроме того, использование SPI требует выделения дополнительных выводов на микроконтроллере, что может усложнить разводку печатной платы. Важно также учитывать, что SPI не предоставляет механизмов обработки ошибок передачи данных, что может снизить надежность работы системы в случае возникновения помех или других проблем в канале связи.
Примеры
Пример SPI на Verilog
Контроллер SPI оптимизирован по ресурсам и основан на памяти ПЛИС на языке Verilog. Имеется один параметр, который регулирует глубину внутреннего буфера. На временной диаграмме(см. ниже) видно, что контроллер SPI принимает данные, начиная передачу с задержкой сигнала SCL в 1 такт. Задержка необходима для обработки данных буфера. Сигнал data_out представляет собой последовательно-параллельный регистр, а valid_data_out подтверждает валидность данных.
module SPI_MASTER
#(
parameter ADDR_RAM = 7 // Параметр для глубины памяти
)
(
input wire [7:0] data_in, // Данные на отправку
input wire clk, // Тактовый сигнал ПЛИС
input wire clk_SPI, // Тактовый сигнал SPI
input wire ena, // Сигнал включения контроллера SPI
output reg valid_data_out, // Валидность принятых данных
output reg [7:0] data_out = 0, // Принятые данные
output wire not_ready, // Готовность контроллера
input wire MISO, // MISO SPI
output reg MOSI, // MOSI SPI
output wire SCL, // SCL SPI
output reg CS // CS SPI
);
// Внутренние регистры
reg transfer_active = 0; // регистр о наличие включения контроллера
reg [(ADDR_RAM - 1 + 3): 0] send_pointer; // указатель отправки
reg [(ADDR_RAM - 1): 0] send_pointer_plis; // указатель считывания
reg latency_cs; // флаг для необходимой задержки памяти
// Внутренние соединения
wire [(ADDR_RAM - 1): 0] re_addr; // адрес чтения памяти
wire clk_sender; // тактовый сигнал памяти
wire [7:0] data_out_ram; // Выход памяти
simple_dual_port_ram #(8, ADDR_RAM) buffer_tx (data_in, send_pointer >> 3,
send_pointer_plis, ena, clk_sender, data_out_ram);
assign SCL = ~clk_SPI & CS,
re_adrr = (CS | latency_cs)? send_pointer >> 3: 0,
clk_sender = (clk_SPI & CS) | (clk & ~CS),
done = send_pointer == (send_pointer_plis << 3),
not_ready = CS | latency_cs;
always @(negedge clk) begin
if (ena) begin
transfer_active <= 1;
send_pointer_plis <= send_pointer_plis + 1;
end else if (done) begin
transfer_active <= 0;
send_pointer_plis <= 0;
end
end
always @(posedge clk_SPI) begin
if (!ena && transfer_active) begin
if (latency_cs) begin
if (done) begin
CS <= 0;
MOSI <= 0;
latency_cs <= 0;
end else begin
CS <= 1;
send_pointer <= send_pointer + 1;
MOSI <= data_out_ram[7 ^ send_pointer];
data_out[0] <= MISO;
data_out[1] <= data_out[0];
data_out[2] <= data_out[1];
data_out[3] <= data_out[2];
data_out[4] <= data_out[3];
data_out[5] <= data_out[4];
data_out[6] <= data_out[5];
data_out[7] <= data_out[6];
valid_data_out = (send_pointer[2: 0] == 0) & CS;
end
end else begin
if (~done) begin
latency_cs <= 1;
end
end
end
end
endmodule
// память контроллера использующий примитив
module simple_dual_port_ram
#(parameter DATA_WIDTH=8, parameter ADDR_WIDTH=6)
(
input [(DATA_WIDTH-1):0] data,
input [(ADDR_WIDTH-1):0] read_addr, write_addr,
input we, clk,
output reg [(DATA_WIDTH-1):0] q
);
reg [DATA_WIDTH-1:0] ram[2**ADDR_WIDTH-1:0];
always @ (posedge clk)
begin
if (we)
ram[write_addr] <= data;
q <= ram[read_addr];
end
endmodule

Пример SPI на STM32
Код для STM32 (STM32F407VGT6) для работы с SPI контроллером в режиме мастера. В этом примере мы инициализируем микроконтроллер в качестве мастера с параметрами CPOL=0 и CPHA=1.
#include "stm32f4xx.h"
void SPI_Init(void){
SPI_InitTypeDef SPI_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStruct.SPI_Mode = SPI_Mode_Master;
SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low;
SPI_InitStruct.SPI_CPHA = SPI_CPHA_1Edge;
SPI_InitStruct.SPI_NSS = SPI_NSS_Soft;
SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;
SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_Init(SPI1, &SPI_InitStruct);
SPI_Cmd(SPI1, ENABLE);
}
uint8_t SPI_Transfer(uint8_t data){
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
SPI_I2S_SendData(SPI1, data);
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
return SPI_I2S_ReceiveData(SPI1);
}
int main(void){
SPI_Init();
uint8_t data_out = 0x55;
uint8_t data_in;
while(1){
data_in = SPI_Transfer(data_out);
// Дальнейшая обработка принятых данных
}
}
Пример SPI на ESP32
#include "driver/spi_master.h"
#define PIN_NUM_MISO 25
#define PIN_NUM_MOSI 23
#define PIN_NUM_CLK 19
#define PIN_NUM_CS 22
void app_main()
{
spi_bus_config_t buscfg = {
.miso_io_num = PIN_NUM_MISO,
.mosi_io_num = PIN_NUM_MOSI,
.sclk_io_num = PIN_NUM_CLK,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = 0
};
spi_device_interface_config_t devcfg = {
.command_bits = 0,
.address_bits = 0,
.dummy_bits = 0,
.mode = 0,
.duty_cycle_pos = 0,
.cs_ena_posttrans = 0,
.cs_ena_pretrans = 0,
.clock_speed_hz = 1000000,
.spics_io_num = PIN_NUM_CS,
.flags = 0,
.queue_size = 1,
.pre_cb = NULL,
.post_cb = NULL
};
spi_device_handle_t spi;
spi_bus_initialize(VSPI_HOST, &buscfg, 1);
spi_bus_add_device(VSPI_HOST, &devcfg, &spi);
spi_transaction_t trans_desc;
memset(&trans_desc, 0, sizeof(trans_desc));
trans_desc.length = 8 * 1; // Длина передаваемых данных в битах
trans_desc.rxlength = 0;
trans_desc.flags = SPI_TRANS_USE_TXDATA;
trans_desc.tx_data[0] = 0x55; // Данные для передачи
spi_device_transmit(spi, &trans_desc);
}