• 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速率

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述
    摩托罗拉协议就是一般的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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    这里有一个问题,就是从机的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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141

    上面程序的结构就是4分频发出去一个SCK信号,然后分频产生的cnt 信号来发送和接收数据,最后有传输开始和传输结束信号,不过这两个信号感觉…,主机不需要别的输入控制,想发的时候发送?那么从机跟不上咋办,就像stm32是串行的,不像FPGA是并行的,所以stm32接受前发送一个起始信号和结束信号

    想想FPGA作为从机的时候:
    打拍收集SCK的信号就行了,然后在对应的主机的设置,在SCK的上升沿或者下降沿收集信号就行。FPGA是并行的,主机发什么,它都能跟上

  • 相关阅读:
    什么商业模式是适合你,元宇宙电商NFG了解一下
    记录:2022-9-17 数组中重复的数据 丑数 II 文件分配方式 位图及使用(打卡)
    【ARC116F】Deque Game 题解
    2023年11月在线IDE流行度最新排名
    生鲜店怎么做微信小程序
    sql---慢查询和语句耗时
    Oracle(55)什么是并行查询(Parallel Query)?
    招商银行 KubeVela 离线部署实践
    计算机组成原理---第五章中央处理器---CPU的功能和基本结构---选择题
    国家网络安全周 | 天空卫士CEO刘霖在《中国网信》杂志发表署名文章
  • 原文地址:https://blog.csdn.net/zerokingwang/article/details/128197859