spi连续无间隔收发任意字节源代码

初次学习spi,写了一个spi主机程序,供学习交流

CPOL = 0:空闲时时钟处于低电平;CPOL = 1:空闲时时钟处于高电平

CPHA = 0:第一个边沿采样,第二个边沿发送 CPHA = 0:第一个边沿发送,第二个边沿采样

主机和从机的行为相同,要么都在一个边沿读,要么都在一个边沿写,不存在主机读的同时从机写或主机写的同时从机读。

模式0:CPOL = 0,CPHA = 0;

模式1:CPOL = 0,CPHA = 1;

模式2:CPOL = 1,CPHA = 0;

模式3:CPOL = 1,CPHA = 1;

目前实现了CPOL = 0,CPHA = 0和CPOL = 1,CPHA = 0两种模式,其他两种模式正在研究

(注:可实现4种模式代码已补充在文末)

简介:用状态机编写,有两个状态:空闲状态和工作状态,空闲状态下不断检测en信号,当en为高,经过一系列处理进入工作状态,工作状态下可连续收发任意字节,时钟无间隔,通过sti信号任意延长工作状态,进而收发任意字节。这里不提供片选信号输出,需要用户来提供,可以提高灵活度。

端口说明:        

rst_n:复位信号,低电平有效

dri_clk:输入时钟

sck:spi输出时钟,频率为dri_clk的4分频,想要特定频率可自行设置dri_clk

MISO:主机输入从机输出

MOSI:主机输出从机输入

en:使能信号,上升沿有效,但不能等到传输结束后仍为高电平,否则会在空闲状态中检测到高电平,从而再次触发。空闲状态下在每个dri_clk的上升沿检测en,从检测到en信号到sck第一个时钟边沿之间有4个dri_clk周期,控制spi片选信号时要用到这个数字。

CPOL:模式选择,CPHA默认为0,在整个工作过程中不能改变

priorH:为0时先发送低位,为1时先发送高位,在整个工作过程中不能改变

data_in:输入的数据,持续时间最少为2个dri_clk周期

data_out:接收的数据,当检测到done的下降沿时,可以读取数据

sti:控制多字节发送,为1时继续发送,为0时在下个字节传输结束后进入空闲状态,持续时间自定

done:与sti配合使用,每当传输一个字节后,done为1,经过2个dri_clk周期后变为0。done为1表示当前字节传输即将结束,在done的上升沿会自动读取sti,在下降沿会自动读取data_in,当首次检测到done为1时,应根据需要改变sti和data_in,在此之后,若检测到done为0,可以读取data_out。若发送n个字节数据,应在检测到第n-1个done时将sti置0。

代码如下:

module spi_CPHA_L
(
	input dri_clk,
	input rst_n,
	input en,
	input CPOL,
	input priorH,
	input sti,
	input [7:0]data_in,
	output reg done,
	output reg sck,
	input MISO,
	output MOSI,
	output reg [7:0]data_out
);
parameter IDLE = 1'b0;
parameter WORK = 1'b1;
reg cur_state;
reg next_state;
reg idle_done;
reg work_done;
always @(*)
	case(cur_state)
		IDLE:if(idle_done)
			next_state = WORK;
		else
			next_state = IDLE;
		WORK:if(work_done)
			next_state = IDLE;
		else
			next_state = WORK;	
	endcase

always @(posedge dri_clk or negedge rst_n)
	if(!rst_n)
		cur_state <= IDLE;
	else
		cur_state <= next_state;
//sck_cnt;
reg [1:0]sck_cnt;
always @(posedge dri_clk or negedge rst_n)
	if(!rst_n)
		sck_cnt <= 2'b0;
	else if(cur_state == WORK)
		sck_cnt <= sck_cnt + 1'b1;
//sck
always @(posedge dri_clk or negedge rst_n)
	if(!rst_n)
		case(CPOL)
			1'b0:sck <= 1'b0;
			1'b1:sck <= 1'b1;
		endcase
	else if(cur_state == WORK)
		if(sck_cnt == 2'd0)
			case(CPOL)
				1'b0:sck <= 1'b1;
				1'b1:sck <= 1'b0;
			endcase
		else if(sck_cnt == 2'd2)
			case(CPOL)
				1'b0:sck <= 1'b0;
				1'b1:sck <= 1'b1;
			endcase
//bit_cnt
reg [2:0]bit_cnt;
always @(posedge dri_clk or negedge rst_n)
	if(!rst_n)
		bit_cnt <= 3'b0;
	else if(cur_state == WORK && sck_cnt == 2'd1)
		bit_cnt <= bit_cnt + 1'b1;
//预置数移位寄存器
reg [8:0]data_in_reg;
always @(posedge p_load or posedge n_load or negedge left or negedge right or negedge rst_n)
	if(!rst_n)
		data_in_reg <= 9'b0;
	else if(p_load || n_load)
		if(priorH)
			data_in_reg[7:0] <= data_in;
		else
			data_in_reg[8:1] <= data_in;
	else if(!left)
		data_in_reg <= data_in_reg << 1;
	else if(!right)
		data_in_reg <= data_in_reg >> 1;
//MOSI,MISO
assign MOSI = (priorH & data_in_reg[8])|(~priorH & data_in_reg[0]);
reg left,right,p_load;
reg [1:0]idle_cnt;
always @(posedge dri_clk or negedge rst_n)
	if(!rst_n)begin
		data_out  <= 8'b0;
		idle_done <= 1'b0;
		idle_cnt  <= 2'b0;
		left   <= 1'b1;
		right  <= 1'b1;
		p_load <= 1'b0;
	end
	else begin
		case(cur_state)
			IDLE:begin
				idle_cnt <= idle_cnt + 1'b1;
				case(idle_cnt)
					2'b00:begin
						case(en)
							1'b0:idle_cnt <= 2'b0;
							1'b1:p_load <= 1'b1;
						endcase
					end
					2'b01:p_load <= 1'b0;
					2'b10:begin
						case(priorH)
							1'b0:right <= 1'b0;
							1'b1:left  <= 1'b0;
						endcase
						idle_done <= 1'b1;
					end
					2'b11:begin
						idle_cnt  <= 2'b0;
						idle_done <= 1'b0;
						left  <= 1'b1;
						right <= 1'b1;
					end
				endcase
			end
			WORK:begin
				left  <= 1'b1;
				right <= 1'b1;
				if(sck_cnt == 2'd0)
					if(priorH)
					   data_out[7-bit_cnt] <= MISO;
					else
					   data_out[bit_cnt] <= MISO;
				else if(sck_cnt == 2'd2)
					if(priorH)
					   left  <= 1'b0;
					else
					   right <= 1'b0;
			end
		endcase
	end
//done,sti
reg sti_reg,n_load;
always @(posedge dri_clk or negedge rst_n)
	if(!rst_n)begin
		done <= 1'b0;
		work_done <= 1'b0;
		sti_reg <= 1'b0;
		n_load <= 1'b0;
	end
	else if(bit_cnt == 3'd7 && sck_cnt == 2'd3)begin
		done <= 1'b1;
		sti_reg <= sti;
	end
	else if(bit_cnt == 3'd7 && sck_cnt == 2'd1)begin
		n_load <= 1'b1;
		done <= 1'b0;
	end
	else if(bit_cnt == 3'd0 && sck_cnt == 2'd2)begin
	    n_load <= 1'b0;
	    case(sti_reg)
	       1'b0:work_done <= 1'b1;
	       1'b1:work_done <= 1'b0;
	    endcase
	end
	else 
		work_done <= 1'b0;
endmodule

测试代码:

`timescale 1ns/1ns
module spi_tb;
reg clk,rst_n,en,CPOL,priorH,sti,MISO;
reg [7:0]data_in;
wire done,sck,MOSI;
wire [7:0]data_out;
spi_CPHA_L u_spi_CPHA_L
(
	.dri_clk(clk),
	.rst_n(rst_n),
	.en(en),
	.CPOL(CPOL),
	.priorH(priorH),
	.sti(sti),
	.data_in(data_in),
	.done(done),
	.sck(sck),
	.MOSI(MOSI),
	.MISO(MISO),
	.data_out(data_out)
);
always #10 clk = ~clk;
initial begin
	clk = 1'b0;
	data_in = 8'b1000_0001;
	sti = 1'b1;
	en = 1'b0;
	CPOL = 1'b1;
	priorH = 1'b1;
	rst_n = 1'b0;
	#40;
	rst_n = 1'b1;
	#40;
	en = 1'b1;
	#200 en = 1'b0;
	wait(done == 1'b1);
	sti = 1'b0;
	data_in = 8'b1100_0001;
	#100;
	data_in = 8'b0000_0000;
end
endmodule

测试波形:

这里演示两个字节的波形

如果有错误之处请大佬们指正。


补充:可以实现4个模式的完整spi代码,使用方法和上述相同

module spi
(
	input dri_clk,
	input rst_n,
	input en,
	input priorH,
	input CPOL,
	input CPHA,
	input sti,
	input [7:0]data_in,
	output reg done,
	output reg sck,
	input MISO,
	output MOSI,
	output reg [7:0]data_out
);
localparam IDLE = 1'b0;
localparam WORK = 1'b1;
reg cur_state;
reg next_state;
reg idle_done;
reg work_done;
reg left,right,p_load;
reg [1:0]idle_cnt;
reg [1:0]sck_cnt;
reg sti_reg,n_load;
reg [2:0]bit_cnt;
reg [8:0]data_in_reg;
wire [1:0]prha;
assign prha = {priorH,CPHA};
always @(*)
	case(cur_state)
		IDLE:if(idle_done)
			next_state = WORK;
		else
			next_state = IDLE;
		WORK:if(work_done)
			next_state = IDLE;
		else
			next_state = WORK;	
	endcase

always @(posedge dri_clk or negedge rst_n)
	if(!rst_n)
		cur_state <= IDLE;
	else
		cur_state <= next_state;
//sck_cnt;
always @(posedge dri_clk or negedge rst_n)
	if(!rst_n)
		sck_cnt <= 2'b0;
	else if(cur_state == WORK)
		sck_cnt <= sck_cnt + 1'b1;
//sck
always @(posedge dri_clk or negedge rst_n)
	if(!rst_n)
		case(CPOL)
			1'b0:sck <= 1'b0;
			1'b1:sck <= 1'b1;
		endcase
	else if(cur_state == WORK)
		if(sck_cnt == 2'd0)
			case(CPOL)
				1'b0:sck <= 1'b1;
				1'b1:sck <= 1'b0;
			endcase
		else if(sck_cnt == 2'd2)
			case(CPOL)
				1'b0:sck <= 1'b0;
				1'b1:sck <= 1'b1;
			endcase
//bit_cnt
always @(posedge dri_clk or negedge rst_n)
	if(!rst_n)
		bit_cnt <= 3'b0;
	else if(cur_state == WORK && sck_cnt == 2'd1)
		bit_cnt <= bit_cnt + 1'b1;
//预置数移位寄存器
always @(posedge p_load or posedge n_load or negedge left or negedge right or negedge rst_n)
	if(!rst_n)
		data_in_reg <= 9'b0;
	else if(p_load || n_load)
		case(prha)
		   2'b00,
		   2'b01:data_in_reg[8:1] <= data_in;
		   2'b10,
		   2'b11:data_in_reg[7:0] <= data_in;
		endcase
	else if(!left)
		data_in_reg <= data_in_reg << 1;
	else if(!right)
		data_in_reg <= data_in_reg >> 1;
//MOSI,MISO
assign MOSI = (priorH & data_in_reg[8])|((~priorH) & data_in_reg[0]);
always @(posedge dri_clk or negedge rst_n)
	if(!rst_n)begin
		data_out  <= 8'b0;
		idle_done <= 1'b0;
		idle_cnt  <= 2'b0;
		left   <= 1'b1;
		right  <= 1'b1;
		p_load <= 1'b0;
	end
	else begin
		case(cur_state)
			IDLE:begin
				idle_cnt <= idle_cnt + 1'b1;
				case(idle_cnt)
					2'b00:begin
						case(en)
							1'b0:idle_cnt <= 2'b0;
							1'b1:p_load <= 1'b1;
						endcase
					end
					2'b01:p_load <= 1'b0;
					2'b10:begin
					    if(!CPHA)
                            case(priorH)
                                1'b0:right <= 1'b0;
                                1'b1:left  <= 1'b0;
                            endcase
						idle_done <= 1'b1;
					end
					2'b11:begin
						idle_cnt  <= 2'b0;
						idle_done <= 1'b0;
						left  <= 1'b1;
						right <= 1'b1;
					end
				endcase
			end
			WORK:begin
				left  <= 1'b1;
				right <= 1'b1;
				if(sck_cnt == 2'd0)
					case(CPHA)
						1'b0:if(priorH)
						     data_out[7-bit_cnt] <= MISO;
						  else
						     data_out[bit_cnt] <= MISO;
						1'b1:if(priorH)
							  left <= 1'b0;
						  else
							  right <= 1'b0;
					endcase
				else if(sck_cnt == 2'd2)
					case(CPHA)
						1'b0:if(priorH)
							  left  <= 1'b0;
						  else
							  right <= 1'b0;
						1'b1:if(priorH)
							  data_out[7-bit_cnt] <= MISO;
						  else
							  data_out[bit_cnt] <= MISO;
					endcase
			end
		endcase
	end
//done,sti
always @(posedge dri_clk or negedge rst_n)
	if(!rst_n)begin
		work_done <= 1'b0;
		sti_reg <= 1'b0;
		n_load <= 1'b0;
		done <= 1'b0;
	end
	else begin
		n_load <= 1'b0;
	   case(CPHA)
	       1'b0:if(bit_cnt == 3'd7 && sck_cnt == 2'd3)begin
	           sti_reg <= sti;
	           done <= 1'b1;
	       end
	       else if(bit_cnt == 3'd7 && sck_cnt == 2'd1)begin
	           done <= 1'b0;
	           n_load <= 1'b1;
	       end
	       1'b1:if(bit_cnt == 3'd7 && sck_cnt == 2'd1)begin
	           sti_reg <= sti;
	           done <= 1'b1;
	       end
	       else if(bit_cnt == 3'd0 && sck_cnt == 2'd3)begin
	           done <= 1'b0;
	           n_load <= 1'b1;
	       end
	   endcase
	   if(bit_cnt == 3'd0 && sck_cnt == 2'd2)begin
	       case(sti_reg)
	          1'b0:work_done <= 1'b1;
	          1'b1:work_done <= 1'b0;
	       endcase
       end
       else 
          work_done <= 1'b0;
	end
endmodule

  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
您好!对于STM32F103系列的微控制器,您可以使用SPI(串行外设接口)和DMA(直接内存访问)来实现同时连续收发。以下是一些步骤和注意事项: 1. 配置SPI控制寄存器: - 确定SPI的工作模式(主模式或从模式),数据位长度,CPOL和CPHA时钟极性和相位等参数。 - 通过设置CR1寄存器来启用SPI外设。 2. 配置DMA控制寄存器: - 确定DMA通道、数据方向(从内存到外设或从外设到内存)、传输长度等参数。 - 配置DMA的源地址和目标地址,以及传输完成后是否自动更新地址。 3. 初始化SPI和DMA: - 配置SPI的GPIO引脚和时钟。 - 初始化DMA通道,并设置传输完成后的回调函数(可选)。 4. 准备数据缓冲区: - 创建一个用于收发数据的缓冲区。 - 在缓冲区中填充要发送数据。 5. 启动DMA传输: - 使用SPI发送函数将数据发送SPI外设。 - 启动DMA传输,将数据从缓冲区传输到SPI外设。 6. 等待传输完成: - 在主循环中等待DMA传输完成的标志位被设置。 7. 处理接收数据: - 使用SPI的接收函数从SPI外设接收数据。 - 在DMA传输完成后,从DMA缓冲区中读取接收到的数据。 请注意,以上步骤是一个简单的示例。具体的代码实现可能会有所不同,具体取决于您使用的开发环境和库。建议参考STM32F103的参考手册和官方示例代码,以获得更详细的信息和正确的使用方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值