SPI通信基础知识
SPI即串行外设接口(Serial Peripheral Interface)
SPI需要至少4根线,单向传输时3根线,它们是MISO(主设备数据输入)、MOSI(主设备数据输出)、SCLK(时钟)和CS/SS(片选)
四种通讯模式
SPI通讯协议一共有四种通讯模式,模式0、模式1、模式2以及模式3,这4种模式分别由时钟极性(CPOL,Clock Polarity)和时钟相位(CPHA,Clock Phase)来定义,其中CPOL参数规定了空闲状态(CS_N为高电平,设备未被选中)时SCK时钟信号的电平状态,CPHA规定了数据采样是在SCK时钟的奇数边沿还是偶数边沿。
SPI通讯协议的4种模式如下,通讯模式时序图,具体见图 46‑3。
模式0:CPOL= 0,CPHA=0。空闲状态时SCK串行时钟为低电平;数据采样在SCK时钟的奇数边沿,本模式中,奇数边沿为上升沿;数据更新在SCK时钟的偶数边沿,本模式中,偶数边沿为下降沿。
模式1:CPOL= 0,CPHA=1。空闲状态时SCK串行时钟为低电平;数据采样在SCK时钟的偶数边沿,本模式中,偶数边沿为下降沿;数据更新在SCK时钟的奇数边沿,本模式中,偶数边沿为上升沿。
模式2:CPOL= 1,CPHA=0。空闲状态时SCK串行时钟为高电平;数据采样在SCK时钟的奇数边沿,本模式中,奇数边沿为下降沿;数据更新在SCK时钟的偶数边沿,本模式中,偶数边沿为上升沿。
模式3:CPOL= 1,CPHA=1。空闲状态时SCK串行时钟为高电平;数据采样在SCK时钟的偶数边沿,本模式中,偶数边沿为上升沿;数据更新在SCK时钟的奇数边沿,本模式中,偶数边沿为下降沿。
我使用了CPOL=1,CPHA=0的模式,即sck闲高,下降沿采样,上升沿数据更新
spi_slave
module spi_slave(
clk , //50MHz时钟
rst_n , //复位
data_in , //要发送的数据
data_out , //接收到的数据
spi_sck , //主机时钟
spi_miso , //主收从发(从机)
spi_mosi , //主发从收(从机)
spi_cs , //主机片选,低有效(从机)
tx_en , //发送使能
tx_done , //发送完成标志位
rx_done //接收完成标志位
);
//改DATA_W的参数即可实现任意字节的发送和接收,现在是两字节发送和接收
parameter DATA_W = 16;
parameter SYNC_W = 2;
//计数器参数
parameter CNT_W = 4;
parameter CNT_N = DATA_W;
input clk;
input rst_n;
input [DATA_W-1:0] data_in;
input spi_sck;
input spi_mosi;
input spi_cs;
input tx_en;
output [DATA_W-1:0] data_out;
output spi_miso;
output tx_done;
output rx_done;
reg [DATA_W-1:0] data_out;
reg spi_miso;
reg tx_done;
reg rx_done;
//中间变量
reg [SYNC_W-1:0] spi_sync;
wire nedge;
wire pedge;
reg spi_mosi_reg;
reg spi_mosi_reg1;
//计数器变量
reg [CNT_W-1:0] cnt_rxbit;
wire add_cnt_rxbit;
wire end_cnt_rxbit;
reg [CNT_W-1:0] cnt_txbit;
wire add_cnt_txbit;
wire end_cnt_txbit;
reg tx_flag;
//边沿检测
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
//SCK时钟空闲状态位高电平,工作模式3
spi_sync <= 2'b11;
else
spi_sync <= {spi_sync[0],spi_sck};
end
assign nedge = spi_sync[1:0] == 2'b10;
assign pedge = spi_sync[1:0] == 2'b01;
//上升沿接收,工作模式三
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt_rxbit <= 0;
else if(add_cnt_rxbit)begin
if(end_cnt_rxbit)
cnt_rxbit <= 0;
else
cnt_rxbit <= cnt_rxbit + 1'b1;
end
end
assign add_cnt_rxbit = pedge;
assign end_cnt_rxbit = add_cnt_rxbit && cnt_rxbit == CNT_N - 1;
//下降沿发送,工作模式三
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt_txbit <= 0;
else if(add_cnt_txbit)begin
if(end_cnt_txbit)
cnt_txbit <= 0;
else
cnt_txbit <= cnt_txbit + 1'b1;
end
end
assign add_cnt_txbit = nedge && tx_flag;
assign end_cnt_txbit = add_cnt_txbit && cnt_txbit == CNT_N - 1;
//因为异步信号同步化的原因,为了与延后的下降沿对齐,多打2拍
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
spi_mosi_reg <= 0;
else
spi_mosi_reg <= spi_mosi;
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
spi_mosi_reg1 <= 0;
else
spi_mosi_reg1 <= spi_mosi_reg;
end
//下降沿 接收主机发来的数据
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
data_out <= 0;
else if(!spi_cs) // cs低电平使能
data_out[CNT_N - 1 - cnt_rxbit ] <= spi_mosi_reg1;
end
//上升沿发送数据
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
spi_miso <= 0;
else if(!spi_cs && tx_flag)
spi_miso <= data_in[CNT_N - 1 - cnt_txbit];
else
spi_miso <= 0;
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
rx_done <= 0;
else if(end_cnt_rxbit)
rx_done <= 1;
else
rx_done <= 0;
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
tx_done <= 0;
else if(end_cnt_txbit)
tx_done <= 1;
else
tx_done <= 0;
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
tx_flag <= 0;
else if(tx_en)
tx_flag <= 1;
else if(end_cnt_txbit)
tx_flag <= 0;
end
endmodule
stm32 cubemx 配置
此处配置为全双工主机模式
注意:
1.spi的速率可根据实际情况修改,但需要注意的是,最好确保和fpga的spi时钟相差好几倍,保证传输的稳定性。
2.spi的通信模式注意和fpga要匹配
3.
串口配置
代码简易示例(16bit)
定义变量
uint16_t aa[1]; //定义变量接收fpga发送的数据
while中
if (HAL_SPI_GetState(&hspi1) == HAL_SPI_STATE_READY)
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);
//HAL_SPI_Receive(&hspi1,aa,2,1000);
HAL_SPI_Receive(&hspi1,(uint8_t *)aa,1,10);
//SendReceive16BitData(data);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
//HAL_UART_Transmit_DMA(&huart1,&a ,1); //利用DMA发送
}
printf("%d\n",aa[0]);
借助vofa+,便可以直观看到fpga通过spi发送给stm32的数据,不过,由于printf比较慢,因此频率比较高的时候,可能会看不出波形。