FPGA学习笔记(九)SPI学习总结及stm32的HAL库下SPI配置

系列文章目录

一、FPGA学习笔记(一)入门背景、软件及时钟约束

二、FPGA学习笔记(二)Verilog语法初步学习(语法篇1)

三、FPGA学习笔记(三) 流水灯入门FPGA设计流程

四、FPGA学习笔记(四)通过数码管学习顶层模块和例化的编写

五、FPGA学习笔记(五)Testbench(测试平台)文件编写进行Modelsim仿真

六、FPGA学习笔记(六)Modelsim单独仿真和Quartus联合仿真

七、FPGA学习笔记(七)verilog的深入学习之任务与函数(语法篇3)

八、FPGA学习笔记(八)同步/异步信号的打拍分析及处理


参考文章

FPGA实现的SPI协议(一)----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:

三线制通讯模式是指SDO/MOSI与SDI/MISO共用一条总线的通讯方式,采用的是半双工通讯

在这里插入图片描述
当然,还有一种只有一个从机的,可以省去片选信号:(但是这不叫三线制)
在这里插入图片描述

四线制SPI:

SDO/MOSI – 主设备数据输出,从设备数据输入,如主机读取命令;

SDI/MISO – 主设备数据输入,从设备数据输,如从机返回数据;

SCLK – 时钟信号,由主设备产生,用于数据同步;

CS/SS – 从设备使能信号,由主设备控制来选择与哪一个从机进行通讯;
在这里插入图片描述


配置SPI工作参数

在这里插入图片描述

配置NSS 引脚的使用模式,可以选择为硬件模式(SPI_NSS_HARD )与软件模式( SPI_NSS_SOFT ),在硬件模式中的SPI 片选信号由SPI 硬件自动产生,而软件模式则需要我们亲自把相应的GPIO 端口拉高或置低产生非片选和片选信号。实际中软件模式应用比较多。
在这里插入图片描述
CRC Calculation:指定是否启用CRC 计算,若我们使用CRC 校验时,就使用这个成员的参数(多项式),来计算CRC 的值。
Baud Rate是计算出的sck速率

CPOL=0:SCK空闲为低电平
CPOL=1:SCK空闲为高电平
CPHA=0:第一个跳变沿采样
CPHA=1:第二个跳变沿采样
CUBEMX里面配置的时候,CPOL选的是high和low,CPHA就是选1 edge和2 edge
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
摩托罗拉协议就是一般的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是并行的,主机发什么,它都能跟上

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值