IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> 串行外设接口(Serial Peripheral Interface SPI) - 逻辑设计部分 -> 正文阅读

[嵌入式]串行外设接口(Serial Peripheral Interface SPI) - 逻辑设计部分


好了确认完整个框架之后,就可以开始SPI的逻辑设计了,还是先设计好再写代码!!!!

1. baud_clk_gen

这个就单纯的根据波特率分频出一个sck时钟,所以比较容易,不解释

还是注意baud_cnt可能取不刀BAUD_CNT_END这个细节。

1.1. 代码

module baud_clk_gen#(
	parameter	BAUD_RATE	=	12500000,
	parameter	CPOL		=	0,
	parameter	CLK_FREQ	=	50000000
	)(
		input	rstn,
		input	clk,
		output	sck
	);

localparam	BAUD_CNT_END = CLK_FREQ / BAUD_RATE;
localparam	BAUD_CNT_WIDTH = $clog2(BAUD_CNT_END);

reg		[BAUD_CNT_WIDTH-1:0]	baud_cnt;

always@(posedge clk) begin
	if(!rstn)
		baud_cnt <= 'b0;
	else if(baud_cnt < BAUD_CNT_END-1)
		baud_cnt <= baud_cnt + 'b1;
	else
		baud_cnt <= 'b0;
end

generate if(CPOL) begin
	always@(posedge clk) begin
		if(!rstn)
			sck <= 'b0;
		else
			sck <= !sck;
	end
end
else begin
	always@(posedge clk) begin
		if(!rstn)
			sck <= 'b1;
		else
			sck <= !sck;
	end
end
endgenerate
endmodule

2. spi_master

先想好master的功能是啥,无论是什么协议发起事务请求的都是Master,即发起写or读操作的都是Master。

例如异步FIFO,永远是被写or被读的,所以异步FIFO就是一辈子Slave打工人

SPI Master也是,任务是对Slave SPI进行读写。但是一想,SPI的全双工是通过循环移位寄存器实现的,发送数据的同时接受了对方的数据,即写必会读、读必会写

这样的话,外部写给spi master一个数据,spi master通过循环移位发给spi slave,没毛病。

但是读怎么读呢?spi master得发给spi slave一个没用的数据,传输结束之后,spi master的移位寄存器存储的才是读spi slave的数据。

所以spi_master状态机可以这么设计

在这里插入图片描述

这个状态图有两个特点:1. 想让Master读出Slave的数据,必须先写 2. 循环移位之后Master从Slave那得到的数据必须读出,否则不能写入。
第2点可以改一下,就是将WAITREAD和IDLE合并,TRANS完了必须在下一次写来临之前读出,否则不能读出

2.1. IDLE:等待写入

IDLE就是等待用户写入,写入完毕的转到TRANS。可以在IDLE将ready一直拉高等待tx_data_val。但有一个问题:tx_data是在clk快时钟域下,必须同步到sck慢时钟域下

多bit数据跨时钟域数据变化快怎么办?添加一个ack?NONONO,通用好用的方法是什么?异步FIFO!

所以IDLE时,SPI Master从FIFO读数,读到数据了就可以进入TRANS,时序也是常见的握手。

在这里插入图片描述

标准握手时序
异步时钟亚稳态 的解决方案——多bit信号

然后就进入TRANS开始移位寄存器循环移位就OK了。但有一个问题,IDLE状态下用户没有指定哪个slave spi被选通怎么办?$csn_i == 1'b1

注意决不能assign csn_o = csn_i;这样用户一选通就直接开始传输了,要是选通了还没从FIFO读出数来咋办?

要是没选通,那就等一等。怎么等?可以为IDLE与TRANS之间添加一个状态等待选通。

2.2. WAIT_CS:等待选通

可以在fifo_rdata_valid==1'b1时判断(!&csn_i)是否成立,成立则将csn_o <= csn_i;并进入TRANS,否则就再加一个状态WAITCS等待选通,如下图

在这里插入图片描述

上图TRANS与csn_o是时序对齐的
当然也可以用1bit reg寄存 是否已经从FIFO读出数来,读出数来了再判断(!&csn_i)

于是状态转换图变成

在这里插入图片描述

2.3. TRANS:传输

传输时由于CPOL和CPHA模式导致spi master对mosi驱动和miso采样在sck不同沿进行,所以根据这两种情况分别设计。

(CPOL ^ CPHA == 0)时

即对应{CPOL,CPHA} == 2'b00 || 2'b11;时,此时是sck上升沿采样miso至移位寄存器、sck下降沿驱动mosi,大端模式

此时移位寄存器内的数据已经准备好,并且是在sck上升沿处驱动csn_o的某位拉低,所以TRANS状态下第一个沿是下降沿,即先驱动、再采样。

在这里插入图片描述

如上图,移位寄存器data_shift位宽为8,那么一定是negedge sck时第7位驱动mosi出去、data_shift[6:0] << 1;,在posedge sck时miso采样到data_shift[0]

所以说移位寄存器data_shift的移位操作是在下降沿进行的,data_shift[0]采样MISO操作时在上升沿进行的,那这个是否违背了触发器单沿触发的设计要求?

就是你不能

always@(posedge clk) begin 	a <= b; end 
always@(negedge clk) begin 	a <= c; end 

违背了,因为IDLE状态下data_shift被FIFO中的fifo_rdata赋值时是在posedge sck,但是TRANS状态下data_shift[7:1]移位操作是在negedge clk进行、data_shift[0]是在posedge sck进行。

所以说,要在IDLE状态下fifo_rdata_val有效时的negedge sckdata_shift[7:1] <= fifo_rdata[7:1];posedge sckdata_shift[0] <= fifo_rdata[0];

移位的问题完事了,那么怎么判断传输结束呢?

还得是计数器,表示有多少位数据没有驱动出去,但是计数器也得是下降沿触发。但是状态转换是在上升沿啊。所以要在计数到0的第一个上升沿立马退出TRANS,但计数器重置8就得在下一个状态的下降沿重置。

在这里插入图片描述

(CPOL ^ CPHA == 1)时

即对应{CPOL,CPHA} == 2'b10 || 2'b01;时,此时是sck下降沿采样miso至移位寄存器、sck上升沿驱动mosi,大端模式

此时移位寄存器内的数据已经准备好,并且是在sck上升沿处驱动csn_o的某位拉低,所以TRANS状态下第一个沿是下降沿,即先采样、再驱动。

先采样的话,data_shift就需要额外1bit存储这个先采样来的值,如下图

在这里插入图片描述

如上图,移位寄存器data_shift位宽为9,那么一定是negedge sck时miso采样到data_shift[0],在posedge sck时驱动data_shift[8]至mosi,并data_shift << 1;

但是这种情况下就没有违背单沿触发要求,因为IDLE状态下fifo_rdata_val有效时data_shift[8:1] <= fifo_rdata;和TRANS状态下data_shift << 1移位均在posedge sck时刻

传输结束的过程也是使用计数器,如下图

在这里插入图片描述

2.4. FIFO_WRITE:写入FIFO

这是另一个异步FIFO,用于将Slave SPI读取的数据存入,以用户读出。但这里需要思考一个问题,这个数据一定要被用户读出吗?

例如我就是想给SPI发一个写地址和写数据,写入就行了,那Slave SPI发回的数据没有任何意义,这样的也要写入FIFO 嘛?

我思考的方法还是帧格式,如图UART那样,用1bit表示该数据是否要写入FIFO即可。

时序图的话就是简单的握手。

在这里插入图片描述

最终得到状态转换图如下

在这里插入图片描述

2.4. 代码

module spi_master#(
	parameter	CHIP_SEL_NUM		=	3,
	parameter	DATA_WIDTH			=	16,
	parameter	ASYNC_FIFO_WIDTH	=	4096,
	parameter	CPOL				=	0,
	parameter	CPHA				=	1
	)(
		input						rstn,
		input						clk,
		
		input	[DATA_WIDTH-1:0]	wr_data,
		input						wr_data_val,
		input						rd_req,
		output	[DATA_WIDTH-1:0]	rd_data,
		output						rd_data_val,
		output						ready,
		
		input						sck,
		output						mosi,
		input						miso,
		input	[CHIP_SEL_NUM-1:0]	csn_i,
		output	[CHIP_SEL_NUM-1:0]	csn_o
	);
	

async_fifo#(
	.DATA_WIDTH			(DATA_WIDTH		 ),
	.ASYNC_FIFO_WIDTH	(ASYNC_FIFO_WIDTH)
	)tx_async_fifo(
		.rstn		(	 ),
		.wclk		(sck ),
		.wdata		(    ),
		.wr_en		(    ),
		.full		(    ),
		.rclk		(    ),
		.rd_en		(    ),
		.rdata		(    ),
		.valid		(    ),
		.empty		(    )
	);
	
	
async_fifo#(
	.DATA_WIDTH			(DATA_WIDTH		 ),
	.ASYNC_FIFO_WIDTH	(ASYNC_FIFO_WIDTH)
	)rx_async_fifo(
		.rstn		(	 ),
		.wclk		(sck ),
		.wdata		(    ),
		.wr_en		(    ),
		.full		(    ),
		.rclk		(    ),
		.rd_en		(    ),
		.rdata		(    ),
		.valid		(    ),
		.empty		(    )
	);

endmodule

3. spi_slave

4. spi

5. spi_tb

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2022-05-18 17:49:03  更:2022-05-18 17:51:01 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 0:47:12-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码