系列文章目录
一、FPGA学习笔记(一)入门背景、软件及时钟约束
二、FPGA学习笔记(二)Verilog语法初步学习(语法篇1)
三、FPGA学习笔记(三) 流水灯入门FPGA设计流程
四、FPGA学习笔记(四)通过数码管学习顶层模块和例化的编写
五、FPGA学习笔记(五)Testbench(测试平台)文件编写进行Modelsim仿真
六、FPGA学习笔记(六)Modelsim单独仿真和Quartus联合仿真
七、FPGA学习笔记(七)verilog的深入学习之任务与函数(语法篇3)
八、FPGA学习笔记(八)同步/异步信号的打拍分析及处理
参考文章
FPGA实现的SPI协议(一)----SPI驱动
文章目录
- 系列文章目录
- 参考文章
- SPI协议概念
- 概念
- 工作方式
- 协议
- stm32的HAL库下的SPI
- 配置SPI工作模式
- 配置SPI工作参数
- 程序代码
- FPGA下的SPI
SPI协议概念
概念
SPI(Serial Peripheral Interface,串行外围设备接口)通讯协议,是一个重要的低速协议。支持全双工通信,且传输速度相对较快,缺点是没有指定的流控制,没有应答机制,在数据可靠性上有一定缺陷。也有人说SPI就是数据交换,想要接收一个数据就必须发送一个数据。一般的实现通常能达到甚至超过10 Mbps。
工作方式
SCK (Serial Clock):时钟信号线,用于同步通讯数据。
MOSI (Master Output, Slave Input):主设备输出/从设备输入引脚。
MISO (Master Input,Slave Output):主设备输入/从设备输出引脚。
CS (Chip Select):片选信号线。当有多个 SPI 从 设备与 SPI 主机相连时,每个从设备都有独立的片选信号线,通过CS片选信号来决定通信的从机设备是哪一台。通信期间低电平有效,表示对应从机被选中。
有的也把CS叫SS( Slave Select),NSS就是(Negetive Slave Select:低电平开始传输)
协议
SPI协议中,时钟上升沿或下降沿读取、空闲时是高电平还是低电平(时钟信号)都是可以选择的。
时钟极性(CPOL,Clock Polarity):规定了SCK时钟信号空闲状态的电平。0低电平,1高电平。
时钟相位(CPHA,Clock Phase):0上升沿,1下降沿。(通常用下降沿采样的少)
SPI 每次传输的单位数不受限制,一般是八个单位,也就是一个字节。在传输过程中,没有规定LSB(Least Significant Bit最低有效位)和MSB(Most Significant Bit最高有效位),一般采用的MSB高位先传输。
stm32的HAL库下的SPI
配置SPI工作模式
这里的主机接收模式相当下图:(后四个相当是单工模式,只支持单方向的传输)
半双工模式(数据传输上支持双方向传输,但是不能同时进行双向传输):
stm32支持半双工模式,可以(通过使能BIDIMODE reg位来)选择单线双向通信模式或单线单向模式。
又在网上找了找,因为还没有实际接触SPI的单线、双线、四线模式,所以只能先简单概括一下:(不一定对)
1.单线模式:就是普通的最常见的SPI接线方式,MOSI主机发从机接,MISO:主机接收从机发送
2.双线模式:MOSI和MISO都用来发送数据,同时单方向传输(感觉变成了IIC方式)
3.四线模式:QSPI是Queued SPI的简写,是Motorola公司推出的SPI接口的扩展,比SPI应用更加广泛。
同时使用MOSI、MISO、WP、HOLD作为数据传输,使得SPI传输带宽增加四倍;
改掉了原来引脚的功能:WP(Write Protect)是防止QSPI Flash的状态寄存器被写入错误的数据,WP信号低电平有效;HOLD信号的作用是暂停QSPI Flash的操作。当HOLD信号为低,并且CS也为低时,串行输出信号DO将处于高阻态,串行输入信号DI与串行时钟信号SCLK将被QSPI Flash忽略。当HOLD拉高以后,QSPI Flash的读写操作能继续进行。当多个SPI设备共享同一组SPI总线相同的信号的时候,可以通过HOLD来切换信号的流向。
配置SPI工作参数
配置NSS 引脚的使用模式,可以选择为硬件模式(SPI_NSS_HARD )与软件模式( SPI_NSS_SOFT ),在硬件模式中的SPI 片选信号由SPI 硬件自动产生,而软件模式则需要我们亲自把相应的GPIO 端口拉高或置低产生非片选和片选信号。实际中软件模式应用比较多。
CRC Calculation:指定是否启用CRC 计算,若我们使用CRC 校验时,就使用这个成员的参数(多项式),来计算CRC 的值。
Baud Rate是计算出的sck速率
摩托罗拉协议就是一般的SPI协议,TI协议的一般指的SSP协议。
程序代码
HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size,
uint32_t Timeout);
HAL_StatusTypeDef HAL_SPI_Transmit_IT(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_SPI_Receive_IT(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_SPI_TransmitReceive_IT(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData,
uint16_t Size);
HAL_StatusTypeDef HAL_SPI_Transmit_DMA(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_SPI_Receive_DMA(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_SPI_TransmitReceive_DMA(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData,
uint16_t Size);
HAL_StatusTypeDef HAL_SPI_DMAPause(SPI_HandleTypeDef *hspi);
HAL_StatusTypeDef HAL_SPI_DMAResume(SPI_HandleTypeDef *hspi);
HAL_StatusTypeDef HAL_SPI_DMAStop(SPI_HandleTypeDef *hspi);
这里有一个问题,就是从机的SCK是input,那么主机如何接收从机数据呢,我想还是通过主机发送数据,从机与此同时接收数据并发送数据,主机才能接收,这也是为什么网上有人说SPI是数据交换。
FPGA下的SPI
以下是FPGA作为主机的时候,是参考文章里面的代码
module spi_drive
(
// 系统接口
input sys_clk , // 全局时钟50MHz
input sys_rst_n , // 复位信号,低电平有效
// 用户接口
input spi_start , // 发送传输开始信号,一个高电平
input spi_end , // 发送传输结束信号,一个高电平
input [7:0] data_send , // 要发送的数据
output reg [7:0] data_rec , // 接收到的数据
output reg send_done , // 主机发送一个字节完毕标志位
output reg rec_done , // 主机接收一个字节完毕标志位
// SPI物理接口
input spi_miso , // SPI串行输入,用来接收从机的数据
output reg spi_sclk , // SPI时钟
output reg spi_cs , // SPI片选信号,低电平有效
output reg spi_mosi // SPI输出,用来给从机发送数据
);
reg [1:0] cnt; //4分频计数器
reg [3:0] bit_cnt_send; //发送计数器
reg [3:0] bit_cnt_rec; //接收计数器
reg spi_end_req; //结束请求
//4分频计数器
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
cnt <= 2'd0;
else if(!spi_cs)begin
if(cnt == 2'd3)
cnt <= 2'd0;
else
cnt <= cnt + 1'b1;
end
else
cnt <= 2'd0;
end
// 生成spi_sclk时钟
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
spi_sclk <= 1'b0; //模式0默认为低电平,CPOL=0
else if(!spi_cs)begin //在SPI传输过程中
if(cnt == 2'd0 )
spi_sclk <= 1'b0;
else if (cnt == 2'd2)
spi_sclk <= 1'b1;
else
spi_sclk <= spi_sclk;
end
else
spi_sclk <= 1'b0; //模式0默认为低电平
end
// 生成片选信号spi_cs
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
spi_cs <= 1'b1; //默认为高电平,低电平选中
else if(spi_start) //开始SPI准备传输,拉低片选信号
spi_cs <= 1'b0;
//收到了SPI结束信号,且结束了最近的一个BYTE
else if(spi_end_req && (cnt == 2'd1 && bit_cnt_rec == 4'd0))
spi_cs <= 1'b1; //拉高片选信号,结束SPI传输
end
// 生成结束请求信号(捕捉spi_end信号)
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
spi_end_req <= 1'b0; //默认不使能
else if(spi_cs)
spi_end_req <= 1'b0; //结束SPI传输后拉低请求
else if(spi_end)
spi_end_req <= 1'b1; //接收到SPI结束信号后就把结束请求拉高
end
// 发送数据过程--------------------------------------------------------------------
// 发送数据
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
spi_mosi <= 1'b0; //模式0空闲
bit_cnt_send <= 4'd0;
end
else if(cnt == 2'd0 && !spi_cs)begin //模式0的上升沿
spi_mosi <= data_send[7-bit_cnt_send]; //发送数据移位
if(bit_cnt_send == 4'd7) //发送完8bit
bit_cnt_send <= 4'd0;
else
bit_cnt_send <= bit_cnt_send + 1'b1;
end
else if(spi_cs)begin //非传输时间段
spi_mosi <= 1'b0; //模式0空闲
bit_cnt_send <= 4'd0;
end
else begin
spi_mosi <= spi_mosi;
bit_cnt_send <= bit_cnt_send;
end
end
// 发送数据标志
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
send_done <= 1'b0;
else if(cnt == 2'd0 && bit_cnt_send == 4'd7) //发送完了8bit数据
send_done <= 1'b1; //拉高一个周期,表示发送完成
else
send_done <= 1'b0;
end
// 接收数据过程--------------------------------------------------------------------
// 接收数据spi_miso
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
data_rec <= 8'd0;
bit_cnt_rec <= 4'd0;
end
else if(cnt == 2'd2 && !spi_cs)begin //模式0的上升沿
data_rec[7-bit_cnt_rec] <= spi_miso; //移位接收
if(bit_cnt_rec == 4'd7) //接收完了8bit
bit_cnt_rec <= 4'd0;
else
bit_cnt_rec <= bit_cnt_rec + 1'b1;
end
else if(spi_cs)begin
bit_cnt_rec <= 4'd0;
end
else begin
data_rec <= data_rec;
bit_cnt_rec <= bit_cnt_rec;
end
end
// 接收数据标志
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
rec_done <= 1'b0;
else if(cnt == 2'd2 && bit_cnt_rec == 4'd7) //接收完了8bit
rec_done <= 1'b1; //拉高一个周期,表示接收完成
else
rec_done <= 1'b0;
end
endmodule
上面程序的结构就是4分频发出去一个SCK信号,然后分频产生的cnt 信号来发送和接收数据,最后有传输开始和传输结束信号,不过这两个信号感觉…,主机不需要别的输入控制,想发的时候发送?那么从机跟不上咋办,就像stm32是串行的,不像FPGA是并行的,所以stm32接受前发送一个起始信号和结束信号
想想FPGA作为从机的时候:
打拍收集SCK的信号就行了,然后在对应的主机的设置,在SCK的上升沿或者下降沿收集信号就行。FPGA是并行的,主机发什么,它都能跟上