[米联客-安路飞龙DR1-FPSOC] FPGA基础篇连载-15 SPI接收程序设计
软件版本:Anlogic -TD5.9.1-DR1_ES1.1
操作系统:WIN10 64bit
硬件平台:适用安路(Anlogic)FPGA
实验平台:米联客-MLK-L1-CZ06-DR1M90G开发板
板卡获取平台:https://milianke.tmall.com/
登录"米联客"FPGA社区 http://www.uisrc.com 视频课程、答疑解惑!
1概述
SPI的接收器驱动程序主要为SPI_CLK和SPI_RX接收数据总线的时序来设计。通过前面的SPI协议学习,我们这里设计的SPI驱动程序需要支持CPHA=0 CPOL=0;CPHA=1 CPOL=0; CPHA=0 CPOL=1; CPHA=1 CPOL=1四种情况。CPHA用于控制SPI接收器的采样时钟位置,CPOL用于设置SPI_CLK的初始电平是高电平还是低电平。
2程序设计
2.1 SPI SLAVE接收驱动器设计
SPI 接收驱动程序包含去毛刺采集、spi_cap stroble模块、bits counter计数器、串并移位模块。
去毛刺:
信号在FPGA内通过连线和逻辑单元时,都会产生延时。延时产生的原因:连线的长短和逻辑单元的数目;受器件的制造工艺、工作电压、温度等条件的影响,所以在信号变化的瞬间,组合逻辑的输出有先后顺序,信号到达端口的时间不一样这种状况成为"竞争",一般在电气特性上表现为高频率的尖脉冲信号,这些信号称为毛刺。然而异步电路没办法做到真正意义上的毛刺消除,只能通过寄存器延迟转成同步电路才能处理毛刺问题。
对SPI的时钟以及选通总线进行采样是异步采样,我们采用多次寄存的方法消除亚稳态
1 //I_spi_clk去毛刺 2 always @(posedge I_clk or negedge I_rstn)begin //SPI时钟信号,进行异步转同步处理 3 if(I_rstn == 1'b0) 4 spi_clk_r <= 4'd0; 5 else 6 spi_clk_r <= {spi_clk_r[2:0],I_spi_clk}; 7 end 8 //I_spi_ss去毛刺 9 always @(posedge I_clk or negedge I_rstn)begin 10 if(I_rstn == 1'b0) 11 spi_ss_r <= 4'd0; 12 else 13 spi_ss_r <= {spi_ss_r[2:0],I_spi_ss}; //将I_spi_ss接收到的数据进行缓存 14 end
SPI-CAP模块:
CHPA和CPOL控制spi_cap,根据CHPA和CPOL设置决定是时钟的上升沿,下降沿亦或者第一个时钟,或者第二个时钟采样。
1 assign spi_clkp = spi_clk_r[3:2]==2'b01; //SPI时钟信号上升沿 2 assign spi_clkn = spi_clk_r[3:2]==2'b10; //SPI时钟信号下降沿 3 4 //CPOL用于控制第一时钟样本或第二时钟样本 5 //capture stroble 设置 6 always @(*)begin 7 if(CPHA)begin 8 if(CPOL) spi_cap = spi_clkp;//CPHA=1 CPOL=1 9 else spi_cap = spi_clkn; //CPHA=1 CPOL=0 10 end 11 else begin 12 if(CPOL) spi_cap = spi_clkn;//CPHA=0 CPOL=1 13 else spi_cap = spi_clkp; //CPHA=0 CPOL=0 14 end 15 end
bits counter:
Bit Counter计数器用于计数了多少bits的采样,对于SPI接收程序,我们增加了对任意单次传输长度的计算,可以支持不仅仅是8bit单字节的传输。
1 //spi bit counter 2 always @(posedge I_clk)begin 3 if(spi_rx_en&&spi_cap&&(spi_bit_cnt < BITS_LEN)) //计数到未到达参数BITS_LEN设定值 4 spi_bit_cnt <= spi_bit_cnt + 1'b1; //spi_bit_cnt计数器+1 5 else if(spi_rx_en==0||spi_bit_cnt == BITS_LEN) //单次传输的长度由参数BITS_LEN来控制 6 spi_bit_cnt <= 0; //计数到达设定值,计数清零 7 end
移位模块:
SPI接收移位模块,在每一个spi cap 有效的时候完成一次数据采样。这里并没有对spi的接收总线进行去除亚稳态处理,因为我们SPI采集可以通过CPHA 和CPOL的控制确保采样时刻总线数据必然是稳定的。
//spi bit shift always @(posedge I_clk)begin if(spi_rx_en&&spi_cap) //spi_cap信号有效时,进行数据采样 spi_rx_r1 <= {spi_rx_r1[BITS_LEN-2:0],I_spi_rx}; //采样的数据进行移位,准备进行下次采样 else if(spi_rx_en == 1'b0) //spi_rx_en拉低,采样结束 spi_rx_r1 <= 0; //spi_rx_r1清零 end
2.2 程序源码
4 RTL仿真
4.1仿真激励文件
Modelsim仿真的创建过程不再重复,如有不清楚的请看前面实验
`timescale 1ns / 1ns//仿真时间刻度/精度 module uispi_rx# ( parameter BITS_LEN = 8, parameter CPOL = 1'b0, parameter CPHA = 1'b0 ) ( input I_clk,//系统时钟输入 input I_rstn,//系统复位输入 input I_spi_clk,//SPI时钟输入 input I_spi_rx,//SPI rx数据输入 input I_spi_ss,//SPI片选信号 output O_spi_rvalid, //SPI rx 接收数据有效信号,当为1的时候spi_rdata数据有效 output [BITS_LEN-1'b1:0] O_spi_rdata//SPI rx接收到的数据输出 ); reg spi_cap = 1'b0; reg [3:0]spi_clk_r = 4'd0; reg [4:0] spi_bit_cnt = 5'd0; reg [BITS_LEN-1'b1:0] spi_rx_r1; reg [3:0]spi_ss_r=4'd0; wire spi_rx_en ; wire spi_clkp ; wire spi_clkn ; assign O_spi_rdata = spi_rx_r1; assign O_spi_rvalid = (spi_bit_cnt == BITS_LEN); assign spi_clkp = spi_clk_r[3:2]==2'b01; //SPI时钟信号上升沿 assign spi_clkn = spi_clk_r[3:2]==2'b10; //SPI时钟信号下降沿 assign spi_rx_en = (~spi_ss_r[3]); //I_spi_ss片选信号持续拉低,使能拉高,接收启动 always @(posedge I_clk or negedge I_rstn)begin //SPI时钟信号,进行异步转同步处理 if(I_rstn == 1'b0) spi_clk_r <= 4'd0; else spi_clk_r <= {spi_clk_r[2:0],I_spi_clk}; end always @(posedge I_clk or negedge I_rstn)begin if(I_rstn == 1'b0) spi_ss_r <= 4'd0; else spi_ss_r <= {spi_ss_r[2:0],I_spi_ss}; //将I_spi_ss接收到的数据进行缓存 end //当总线空闲时,当CPHA=1时,SCL=1;当CPHA=0时,则SCL=0 //CPOL用于控制第一时钟样本或第二时钟样本 //capture stroble 设置 always @(*)begin if(CPHA)begin if(CPOL) spi_cap = spi_clkp;//CPHA=1 CPOL=1 else spi_cap = spi_clkn; //CPHA=1 CPOL=0 end else begin if(CPOL) spi_cap = spi_clkn;//CPHA=0 CPOL=1 else spi_cap = spi_clkp; //CPHA=0 CPOL=0 end end //spi bit counter always @(posedge I_clk)begin if(spi_rx_en&&spi_cap&&(spi_bit_cnt < BITS_LEN)) //计数到未到达参数BITS_LEN设定值 spi_bit_cnt <= spi_bit_cnt + 1'b1; //spi_bit_cnt计数器+1 else if(spi_rx_en==0||spi_bit_cnt == BITS_LEN) //单次传输的长度由参数BITS_LEN来控制 spi_bit_cnt <= 0; //计数到达设定值,计数清零 end //spi bit shift always @(posedge I_clk)begin if(spi_rx_en&&spi_cap) //spi_cap信号有效时,进行数据采样 spi_rx_r1 <= {spi_rx_r1[BITS_LEN-2:0],I_spi_rx}; //采样的数据进行移位,准备进行下次采样 else if(spi_rx_en == 1'b0) //spi_rx_en拉低,采样结束 spi_rx_r1 <= 0; //spi_rx_r1清零 end endmodule
本实验以仿真的方式演示,仿真激励信号提供一个系统时钟即可
1 `timescale 1ns / 1ps 2 3 module master_spi_tb; 4 5 localparam BYTES = 8; 6 localparam TCNT = BYTES*8*2-1; 7 8 localparam CPOL = 1; 9 localparam CPHA = 1; 10 11 reg I_clk; //系统时钟 12 reg [7:0] i;//计数器,用于产生SPI时钟数量 13 reg I_rstn; //系统复位 14 reg I_spi_clk;//SPI时钟 15 reg I_spi_ss; //SPI的Slave选通信号 16 reg [3:0]bit_cnt; //bit计数器 17 reg [7:0]spi_tx_buf; //发送缓存(移位寄存器) 18 reg [7:0]spi_tx_buf_r; //发送化缓存,用于产生测试数据 19 reg first_data_flag; //是否一个时钟改变数据 20 21 wire O_spi_rvalid; //SPI 数据接收有效,当该信号有效代表接收到一个有效数据 22 wire [7:0]O_spi_rdata; //SPI读数据 23 wire I_spi_rx;//SPI数据总线 24 25 //tb模拟的SPI测试数据接到I_spi_rx 26 assign I_spi_rx = spi_tx_buf[7]; 27 28 //例化SPI 接收模块 29 uispi_rx# 30 ( 31 .BITS_LEN(8), 32 .CPOL(CPOL), 33 .CPHA(CPHA) 34 ) 35 I_spi_rxnst( 36 .I_clk(I_clk), 37 .I_rstn(I_rstn), 38 .I_spi_clk(I_spi_clk), 39 .I_spi_rx(I_spi_rx), 40 .I_spi_ss(I_spi_ss), 41 .O_spi_rvalid(O_spi_rvalid), 42 .O_spi_rdata(O_spi_rdata) 43 ); 44 45 initial begin 46 I_clk = 1'b0; 47 I_rstn = 1'b0; 48 #100; 49 I_rstn = 1'b1; 50 end 51 52 always #10 I_clk = ~I_clk; //时钟信号翻转,产生系统时钟 53 54 initial begin 55 #100; 56 i = 0; 57 58 forever begin 59 I_spi_clk = CPOL; //设置时钟极性 60 I_spi_ss = 1; // 设置SPI的SS控制信号 61 #2000; 62 I_spi_ss = 0; 63 for(i=0;i<TCNT;i=i+1) #1000 I_spi_clk = ~ I_spi_clk; //产生SPI时钟 64 #2000; 65 I_spi_ss = 1; 66 67 end 68 end 69 70 initial begin 71 #100; 72 bit_cnt = 0; 73 first_data_flag =0; 74 spi_tx_buf[7:0] = 8'ha0; 75 spi_tx_buf_r[7:0] = 8'ha0; 76 forever begin 77 //spi ss 控件用于启用传输 78 wait(I_spi_ss);//spi ss 79 bit_cnt = 0; 80 spi_tx_buf[7:0] = 8'ha0; 81 spi_tx_buf_r[7:0] = 8'ha0; 82 83 if((CPHA == 1 && CPOL ==0)||(CPHA == 1 && CPOL ==1))//第一个时钟沿改变数据的情况 84 first_data_flag = 1; //设置first_data_flag=1 下面的发送时序对应情况跳过第一个沿 85 86 //ss低时开始数据传输 87 wait(!I_spi_ss); 88 89 while(!I_spi_ss)begin 90 91 //COPL=0 CPHA=0默认SCLK为低电平,对于发送方,在对于第1个bit数据提前放到总线 92 if(CPHA == 0 && CPOL ==0)begin 93 @(negedge I_spi_clk) begin //每个时钟的下降沿更新需要发送的BIT 94 if(bit_cnt == 7)begin//连续发送过程中,8bits 发送完毕后更新数据 95 bit_cnt = 0; 96 spi_tx_buf_r = spi_tx_buf_r + 1'b1;//产生新的测试数据 97 spi_tx_buf = spi_tx_buf_r;//重新跟新发送寄存器 98 end 99 else begin 100 spi_tx_buf = {spi_tx_buf[6:0],1'b0};//数据移位,更新数据 101 bit_cnt = bit_cnt + 1'b1;//SPI发送位数计数器 102 end 103 end 104 end 105 106 //CPHA=0 COPL=1 默认SCLK为高电平,对于发送方,在对于第1个bit数据提前放到总线 107 if(CPHA == 0 && CPOL ==1)begin 108 @(posedge I_spi_clk) begin //每个时钟的上升沿更新需要发送的BIT 109 if(bit_cnt == 7)begin //连续发送过程中,8bits 发送完毕后更新数据 110 bit_cnt = 0; 111 spi_tx_buf_r = spi_tx_buf_r + 1'b1;//产生新的测试数据 112 spi_tx_buf = spi_tx_buf_r; //重新跟新发送寄存器 113 end 114 else begin 115 spi_tx_buf = {spi_tx_buf[6:0],1'b0};//数据移位,更新数据 116 bit_cnt = bit_cnt + 1'b1;//SPI发送位数计数器 117 end 118 end 119 end 120 121 //CPHA=1 COPL=0 默认SCLK为低电平,对于发送方,在第1个SCLK的跳变沿更新 122 if(CPHA == 1 && CPOL ==0)begin 123 @(posedge I_spi_clk) begin 124 if(first_data_flag == 1'b1)begin //第一个时钟沿,由于前面已经提前初始化第一个需要发送的数据,因此,这里跳过第一个跳变沿沿 125 first_data_flag = 0; 126 //spi_tx_buf[7:0] = 8'ha0;//也可以在第一个跳变沿初始化第一个发送的数据 127 end 128 else begin 129 if(bit_cnt == 7)begin 130 bit_cnt = 0; 131 spi_tx_buf_r = spi_tx_buf_r + 1'b1;//产生新的测试数据 132 spi_tx_buf = spi_tx_buf_r;//重新跟新发送寄存器 133 end 134 else begin 135 spi_tx_buf = {spi_tx_buf[6:0],1'b0}; //数据移位,更新数据 136 bit_cnt = bit_cnt + 1'b1;//SPI发送位数计数器 137 end 138 end 139 end 140 end 141 142 //CPHA=1 COPL=1 默认SCLK为高电平,对于发送方,在第1个SCLK的跳变沿更新 143 if(CPHA == 1 && CPOL ==1)begin 144 @(negedge I_spi_clk) begin 145 if(first_data_flag == 1'b1)begin //第一个时钟沿,由于前面已经提前初始化第一个需要发送的数据,因此,这里跳过第一个跳变沿沿 146 first_data_flag = 0; 147 //spi_tx_buf[7:0] = 8'ha0;//也可以在第一个跳变沿初始化第一个发送的数据 148 end 149 else begin 150 if(bit_cnt == 7)begin 151 bit_cnt = 0; 152 spi_tx_buf_r = spi_tx_buf_r + 1'b1;//产生新的测试数据 153 spi_tx_buf = spi_tx_buf_r;//重新跟新发送寄存器 154 end 155 else begin 156 spi_tx_buf = {spi_tx_buf[6:0],1'b0};//数据移位,更新数据 157 bit_cnt = bit_cnt + 1'b1;//SPI发送位数计数器 158 end 159 end 160 end 161 end 162 163 end 164 end 165 end 166 167 endmodule
以下启动modelsim仿真
4.2 SPI接收驱动代码仿真CPHA=0 CPOL=0
如下图所示,当CPHA=0 CPOL=0,代表SPI的SCLK默认是低电平,SPI接收器在SCLK第1个时钟沿采样。SPI发送驱动器数据在SCLK的第2个时钟沿更新,确保SPI下一个SCLK的第1个时钟沿数据有足够的建立和保持时间。下图以发送8'ha0为例。
4.3 SPI发送驱动代码仿真CPHA=1 CPOL=0
如下图所示,当CPHA=1 CPOL=0,代表SPI的SCLK默认是低电平,SPI接收器在SCLK第2个时钟沿采样。SPI发送驱动器数据在下一个SCLK的第1个时钟沿更新,确保SPI下一个SCLK的第2个时钟沿数据有足够的建立和保持时间。下图以发送8'h02为例。
4.4 SPI接收驱动代码仿真CPHA=0 CPOL=1
和CPHA=0 CPOL=0这种设置相比,时钟SCLK取反
4.5 SPI接收驱动代码仿真CPHA=1 CPOL=1
和CPHA=1 CPOL=0这种设置相比,时钟SCLK取反
本文来米联客(milianke),作者:米联客(milianke),转载请注明原文链接:https://www.cnblogs.com/milianke/p/18330296