赞
踩
SPI的通信原理很简单,它以主从方式工作,这种模式通常有一个主设备和一个或多个从设备,需要至少4根线,事实上3根也可以(单向传输时)。也是所有基于SPI的设备共有的,它们是MISO(主设备数据输入)、MOSI(主设备数据输出)、SCLK(时钟)、CS(片选)。
(1)MISO– Master Input Slave Output,主设备数据输入,从设备数据输出;
(2)MOSI– Master Output Slave Input,主设备数据输出,从设备数据输入;
(3)SCLK – Serial Clock,时钟信号,由主设备产生;
(4)CS – Chip Select,从设备使能信号,由主设备控制。
16Mbit的存储空间
单扇区擦除或者整块擦除
用spi协议与flash读写
C是串行时钟
D是数据
S是片选信号
flash只支持mode0和mode3两种模式
CPOL时钟相位
时钟的极性(CPOL)用来决定在总线空闲时,同步时钟(SCK)信号线上的电位是高电平还是低电平。当时钟极性为0时(CPOL=0),SCK信号线在空闲时为低电平;当时钟极性为1时(CPOL=1),SCK信号线在空闲时为高电平;
CPHA时钟极性
当时钟相位为1时(CPHA=1),在SCK信号线的第二个跳变沿进行采样;这里的跳变沿究竟是上升沿还是下降沿?取决于时钟的极性。当时钟极性为0时,取下降沿;当时钟极性为1时,取上升沿
CPOL=0,CPHA=1
CPOL=1,CPHA=0
CPOL=1,CPHA=1
MSB先,就是高字节先
Instruction | Code | 说明 |
---|---|---|
RDID | 8’h9F | 读ID |
RDSR | 8’h05 | 读寄存器判断最后一位是0(但我其实没有用过) |
READ | 8’h03 | 读数据 |
PP | 8’h02 | 页擦除 |
SE | 8’hD8 | 扇区擦除 |
判断WIP BIT是否为0才能进行下一步(我的代码里没有用到)
时间名称 | ||
---|---|---|
C_TIME | 100ns | 一个操作结束后到下一个操作片需要100ns,比如一个命令到下一个命令 |
PP_TIME | 1.4-5ms | 页编程的所需要的时间5ms |
SE_TIME | 1-3s | 擦除所需要的时间3s |
module spi_interface( input clk, input rst_n, // 接口与主机 input [7:0] din, input req, output [7:0] dout, output done, // 接口与flash input miso,// 主机采样从机发送 output mosi,// 主机发送从机 output sclk,// 串行时钟 output cs_n // 片选信号 ); parameter CPHA = 1,// 空闲状态高电平 CPOL = 1;// 下降沿发送,上升沿采样 // 16分频或8分频或4分频 不能2分频 parameter SCLK = 16, SCLK_BEFORE = SCLK/4, SCLK_AFTER = SCLK*3/4; // 状态机 localparam IDLE = 4'b0001, WAIT = 4'b0010, DATA = 4'b0100, DONE = 4'b1000; reg [3:0] state_c; reg [3:0] state_n; wire idle2wait; wire wait2data; wire data2done; wire done2idle; // bit计数器 reg [2:0] cnt_bit; wire add_cnt_bit; wire end_cnt_bit; // 分频串行时钟计数器 reg [4:0] cnt_sclk; wire add_cnt_sclk; wire end_cnt_sclk; // 寄存要发送的数据 reg spi_sclk; reg [7:0] rx_data; reg [7:0] tx_data; reg spi_cn_n; always @(posedge clk or negedge rst_n)begin if(!rst_n)begin state_c <= IDLE; end else begin state_c <= state_n; end end always @(*)begin case (state_c) IDLE :begin if(idle2wait)begin state_n = WAIT; end else begin state_n = state_c; end end WAIT :begin if(wait2data)begin state_n = DATA; end else begin state_n = state_c; end end DATA :begin if(data2done)begin state_n = DONE; end else begin state_n = state_c; end end DONE :begin if(done2idle)begin state_n = IDLE; end else begin state_n = state_c; end end default: state_n = IDLE; endcase end assign idle2wait = state_c == IDLE && (req); assign wait2data = state_c == WAIT && (1'b1); assign data2done = state_c == DATA && (end_cnt_bit); assign done2idle = state_c == DONE && (1'b1); // bit计数器 always @(posedge clk or negedge rst_n)begin if(!rst_n)begin cnt_bit <= 0; end else if(add_cnt_bit)begin if(end_cnt_bit)begin cnt_bit <= 0; end else begin cnt_bit <= cnt_bit + 1; end end else begin cnt_bit <= cnt_bit; end end assign add_cnt_bit = end_cnt_sclk; assign end_cnt_bit = add_cnt_bit && cnt_bit == 8 - 1; // sclk计数器 always @(posedge clk or negedge rst_n)begin if(!rst_n)begin cnt_sclk <= 0; end else if(add_cnt_sclk)begin if(end_cnt_sclk)begin cnt_sclk <= 0; end else begin cnt_sclk <= cnt_sclk + 1; end end else begin cnt_sclk <= cnt_sclk; end end assign add_cnt_sclk = (state_c == DATA); assign end_cnt_sclk = add_cnt_sclk && cnt_sclk == SCLK - 1; // 16分频串行时钟 CPHA=1,CPOL=1 always @(posedge clk or negedge rst_n)begin if(!rst_n)begin if(CPHA == 0)begin spi_sclk <= 1'b0; end else if(CPHA == 1)begin spi_sclk <= 1'b1; end end else if(add_cnt_sclk && cnt_sclk == SCLK_BEFORE - 1)begin if(CPHA == 0)begin spi_sclk <= 1'b1; end else if(CPHA == 1)begin spi_sclk <= 1'b0; end end else if(add_cnt_sclk && cnt_sclk == SCLK_AFTER - 1)begin if(CPHA == 0)begin spi_sclk <= 1'b0; end else if(CPHA == 1)begin spi_sclk <= 1'b1; end end end // 发送的数据mosi 高位MSB先 CPHA=1,CPOL=1 always @(posedge clk or negedge rst_n)begin if(!rst_n)begin tx_data <= 0; end else if(CPOL == 0)begin if(add_cnt_sclk && cnt_sclk == SCLK_AFTER - 1)begin tx_data <= din; end end else if(CPOL == 1)begin if(add_cnt_sclk && cnt_sclk == SCLK_BEFORE - 1)begin tx_data <= din; end end end // 接收的数据miso 高位MSB先 CPHA=1,CPOL=1 always @(posedge clk or negedge rst_n)begin if(!rst_n)begin rx_data <= 0; end else if(CPOL == 0)begin if(add_cnt_sclk && cnt_sclk == SCLK_BEFORE - 1)begin rx_data[7-cnt_bit] <= miso; end end else if(CPOL == 1)begin if(add_cnt_sclk && cnt_sclk == SCLK_AFTER - 1)begin rx_data[7-cnt_bit] <= miso; end end end assign mosi = tx_data[7-cnt_bit]; assign sclk = spi_sclk; assign cs_n = ~req; assign dout = rx_data; assign done = (state_c == DONE); endmodule
module spi_read_ctrl( input clk, input rst_n, input [2:0] key_out, input [7:0] din, input done, output reg req, output [7:0] dout, output reg [23:0] seg_data ); localparam RDID_CMD = 8'h9F,// 读ID指令 RDDA_CMD = 8'h03,// 读数据指令 RDDA_ADD = 24'h0;// 读数据地址 localparam IDLE = 7'b000_0001, RDIDCMD = 7'b000_0010, RDID = 7'b000_0100, RDDACMD = 7'b000_1000, RDDAADD = 7'b001_0000, RDDATA = 7'b010_0000, DONE = 7'b100_0000; reg [6:0] state_c; reg [6:0] state_n; wire idle2rdidcmd ; wire idle2rddacmd ; wire rdidcmd2rdid ; wire rdid2done ; wire rddacmd2rddaadd; wire rddaadd2rddata ; wire rddata2done ; wire done2idle ; // 字节计数器 reg [2:0] cnt_byte; wire add_cnt_byte; wire end_cnt_byte; // 读id和读数据请求 reg rdid_req; reg rdda_req; reg [7:0] tx_data; // 状态机 always @(posedge clk or negedge rst_n)begin if(!rst_n)begin state_c <= IDLE; end else begin state_c <= state_n; end end always @(*)begin case (state_c) IDLE :begin if(idle2rdidcmd)begin state_n = RDIDCMD; end else if(idle2rddacmd)begin state_n = RDDACMD; end else begin state_n = state_c; end end RDIDCMD :begin if(rdidcmd2rdid)begin state_n = RDID; end else begin state_n = state_c; end end RDID :begin if(rdid2done)begin state_n = DONE; end else begin state_n = state_c; end end RDDACMD :begin if(rddacmd2rddaadd)begin state_n = RDDAADD; end else begin state_n = state_c; end end RDDAADD :begin if(rddaadd2rddata)begin state_n = RDDATA; end else begin state_n = state_c; end end RDDATA :begin if(rddata2done)begin state_n = DONE; end else begin state_n = state_c; end end DONE :begin if(done2idle)begin state_n = IDLE; end else begin state_n = state_c; end end default: state_n = IDLE; endcase end assign idle2rdidcmd = state_c == IDLE && (rdid_req); assign idle2rddacmd = state_c == IDLE && (rdda_req); assign rdidcmd2rdid = state_c == RDIDCMD && (end_cnt_byte); assign rdid2done = state_c == RDID && (end_cnt_byte); assign rddacmd2rddaadd = state_c == RDDACMD && (end_cnt_byte); assign rddaadd2rddata = state_c == RDDAADD && (end_cnt_byte); assign rddata2done = state_c == RDDATA && (end_cnt_byte); assign done2idle = state_c == DONE && (1'b1); // 字节计数器 always @(posedge clk or negedge rst_n)begin if(!rst_n)begin cnt_byte <= 0; end else if(add_cnt_byte)begin if(end_cnt_byte)begin cnt_byte <= 0; end else begin cnt_byte <= cnt_byte + 1; end end else begin cnt_byte <= cnt_byte; end end assign add_cnt_byte = (state_c != IDLE) && done; assign end_cnt_byte = add_cnt_byte && cnt_byte == (((state_c == RDIDCMD) || (state_c == RDDACMD) || (state_c == RDDATA))?(1-1):(3-1)); // 读id和读数据请求 always @(posedge clk or negedge rst_n)begin if(!rst_n)begin rdid_req <= 0; rdda_req <= 0; req <= 0; end else if(key_out[0])begin rdid_req <= 1'b1; req <= 1'b1; end else if(key_out[1])begin rdda_req <= 1'b1; req <= 1'b1; end else if(state_c == DONE)begin req <= 1'b0; rdid_req <= 1'b0; rdda_req <= 1'b0; end end // 指令 always @(posedge clk or negedge rst_n)begin if(!rst_n)begin tx_data <= 0; end else if(idle2rdidcmd)begin tx_data <= RDID_CMD; end else if(idle2rddacmd)begin tx_data <= RDDA_CMD; end else if(rddacmd2rddaadd)begin tx_data <= RDDA_ADD; end end // seg_data always @(posedge clk or negedge rst_n)begin if(!rst_n)begin seg_data <= 0; end else if(state_c == RDID && add_cnt_byte)begin case(cnt_byte) 0 : seg_data[23:16] <= din; 1 : seg_data[15:8] <= din; 2 : seg_data[7:0] <= din; default: seg_data <= seg_data; endcase end else if(state_c == RDDATA && add_cnt_byte)begin case(cnt_byte) 0 : seg_data[23:16] <= din; default: seg_data <= seg_data; endcase end else begin seg_data <= seg_data; end end // assign req = rdid_req || rdda_req; assign dout = tx_data; endmodule
module spi_write_ctrl( input clk, input rst_n, input [2:0] key_out, input [7:0] din, input done, output req, output [7:0] dout ); parameter CMD_TIME = 10,// 第一个指令到下一个指令200ns等待时间 PP_TIME = 250_000,// PP可编程时间5ms SE_TIME = 150_000_000;// SE擦除时间3s parameter WREN_CMD = 8'h06, SE_CMD = 8'hD8, SE_ADD = 24'h000000, RDSR_CMD = 8'h05, PP_CMD = 8'h02, PP_ADD = 24'h000000, DATA = 8'h78; // 状态机 localparam IDLE =10'b00000_00001, FIRWRENCMD =10'b00000_00010, SECMD =10'b00000_00100, SEADD =10'b00000_01000, RDSRCMD =10'b00000_10000, SECWRENCMD =10'b00001_00000, PPCMD =10'b00010_00000, PPADD =10'b00100_00000, PPDATA =10'b01000_00000, DONE =10'b10000_00000; reg [9:0] state_c; reg [9:0] state_n; wire idle2firwrencmd ; wire firwrencmd2secmd ; wire secmd2seadd ; wire seadd2rdsrcmd ; wire rdsrcmd2secwrencmd ; wire secwrencmd2ppcmd ; wire ppcmd2ppadd ; wire ppadd2ppdata ; wire ppdata2done ; wire done2idle ; // 字节计数器 reg [1:0] cnt_byte; wire add_cnt_byte; wire end_cnt_byte; // 100ms一个命令到下一个命令的等待时间 reg [3:0] cnt_200ns; wire add_cnt_200ns; wire end_cnt_200ns; // se擦除等待时间 reg [27:0] cnt_3s; wire add_cnt_3s; wire end_cnt_3s; // pp页编程等待时间 // reg cnt_5ms; // wire add_cnt_5ms; // wire end_cnt_5ms; // 一个命令到下一个命令的等待标志 reg delay_flag; // 寄存req reg req_r; // 寄存要发送的数据 reg [7:0] tx_data; always @(posedge clk or negedge rst_n)begin if(!rst_n)begin state_c <= IDLE; end else begin state_c <= state_n; end end always @(*)begin case (state_c) IDLE :begin if(idle2firwrencmd)begin state_n = FIRWRENCMD; end else begin state_n = state_c; end end FIRWRENCMD :begin if(firwrencmd2secmd)begin state_n = SECMD; end else begin state_n = state_c; end end SECMD :begin if(secmd2seadd)begin state_n = SEADD; end else begin state_n = state_c; end end SEADD :begin if(seadd2rdsrcmd)begin state_n = RDSRCMD; end else begin state_n = state_c; end end RDSRCMD :begin if(rdsrcmd2secwrencmd)begin state_n = SECWRENCMD; end else begin state_n = state_c; end end SECWRENCMD :begin if(secwrencmd2ppcmd)begin state_n = PPCMD; end else begin state_n = state_c; end end PPCMD :begin if(ppcmd2ppadd)begin state_n = PPADD; end else begin state_n = state_c; end end PPADD :begin if(ppadd2ppdata)begin state_n = PPDATA; end else begin state_n = state_c; end end PPDATA :begin if(ppdata2done)begin state_n = DONE; end else begin state_n = state_c; end end DONE :begin if(done2idle)begin state_n = IDLE; end else begin state_n = state_c; end end default: state_n = IDLE; endcase end assign idle2firwrencmd = state_c == IDLE && (key_out[2]); assign firwrencmd2secmd = state_c == FIRWRENCMD && (end_cnt_200ns); assign secmd2seadd = state_c == SECMD && (end_cnt_byte); assign seadd2rdsrcmd = state_c == SEADD && (end_cnt_3s); assign rdsrcmd2secwrencmd = state_c == RDSRCMD && (end_cnt_200ns); assign secwrencmd2ppcmd = state_c == SECWRENCMD && (end_cnt_200ns); assign ppcmd2ppadd = state_c == PPCMD && (end_cnt_byte); assign ppadd2ppdata = state_c == PPADD && (end_cnt_byte); assign ppdata2done = state_c == PPDATA && (end_cnt_byte); assign done2idle = state_c == DONE && (1'b1); // 字节计数器 always @(posedge clk or negedge rst_n)begin if(!rst_n)begin cnt_byte <= 0; end else if(add_cnt_byte)begin if(end_cnt_byte)begin cnt_byte <= 0; end else begin cnt_byte <= cnt_byte + 1; end end else begin cnt_byte <= cnt_byte; end end assign add_cnt_byte = ((state_c != IDLE) && done); assign end_cnt_byte = add_cnt_byte && cnt_byte == (((state_c == FIRWRENCMD) || (state_c == SECMD) || (state_c == RDSRCMD) || (state_c == SECWRENCMD) || (state_c == PPCMD) || (state_c == PPDATA))?(1-1):(3-1)); // 100ms一个命令到下一个命令的等待时间计数器 always @(posedge clk or negedge rst_n)begin if(!rst_n)begin cnt_200ns <= 0; end else if(add_cnt_200ns)begin if(end_cnt_200ns)begin cnt_200ns <= 0; end else begin cnt_200ns <= cnt_200ns + 1; end end else begin cnt_200ns <= cnt_200ns; end end assign add_cnt_200ns = (((state_c == FIRWRENCMD) || (state_c == RDSRCMD) || (state_c == SECWRENCMD)) && delay_flag); assign end_cnt_200ns = add_cnt_200ns && cnt_200ns == CMD_TIME - 1; // SE擦除时间2s always @(posedge clk or negedge rst_n)begin if(!rst_n)begin cnt_3s <= 0; end else if(add_cnt_3s)begin if(end_cnt_3s)begin cnt_3s <= 0; end else begin cnt_3s <= cnt_3s + 1; end end else begin cnt_3s <= cnt_3s; end end assign add_cnt_3s = ((state_c == SEADD) && delay_flag); assign end_cnt_3s = add_cnt_3s && cnt_3s == SE_TIME - 1; // 一个命令到下一个命令的等待延长标志 always @(posedge clk or negedge rst_n)begin if(!rst_n)begin delay_flag <= 0; end else if(end_cnt_byte)begin delay_flag <= 1'b1; end else if(end_cnt_200ns || end_cnt_3s)begin delay_flag <= 1'b0; end else begin delay_flag <= delay_flag; end end // req信号 always @(posedge clk or negedge rst_n)begin if(!rst_n)begin req_r <= 1'b0; end else if(idle2firwrencmd)begin req_r <= 1'b1; end else if((state_c == FIRWRENCMD) && end_cnt_byte)begin req_r <= 1'b0; end else if(firwrencmd2secmd)begin req_r <= 1'b1; end else if(secmd2seadd)begin req_r <= 1'b1; end else if((state_c == SEADD) && end_cnt_byte)begin req_r <= 1'b0; end else if(seadd2rdsrcmd)begin req_r <= 1'b1; end else if((state_c == RDSRCMD) && end_cnt_byte)begin req_r <= 1'b0; end else if(rdsrcmd2secwrencmd)begin req_r <= 1'b1; end else if((state_c == SECWRENCMD) && end_cnt_byte)begin req_r <= 1'b0; end else if(secwrencmd2ppcmd)begin req_r <= 1'b1; end else if(ppcmd2ppadd)begin req_r <= 1'b1; end else if(ppadd2ppdata)begin req_r <= 1'b1; end else if(ppdata2done)begin req_r <= 1'b0; end end // dout传输的数据 always @(posedge clk or negedge rst_n)begin if(!rst_n)begin tx_data <= 0; end else if(state_c == FIRWRENCMD)begin tx_data <= WREN_CMD; end else if(state_c == SECMD)begin tx_data <= SE_CMD; end else if(state_c == SEADD)begin tx_data <= SE_ADD; end else if(state_c == RDSRCMD)begin tx_data <= RDSR_CMD; end else if(state_c == SECWRENCMD)begin tx_data <= WREN_CMD; end else if(state_c == PPCMD)begin tx_data <= PP_CMD; end else if(state_c == PPADD)begin tx_data <= PP_ADD; end else if(state_c == PPDATA)begin tx_data <= DATA; end end assign req = req_r; assign dout = tx_data; endmodule
module spi_control( input clk, input rst_n, input [2:0] key_out, input [7:0] din, input done, output [7:0] dout, output req, output [23:0] seg_data ); wire rd_req; wire wr_req; wire [7:0] rd_tx_data; wire [7:0] wr_tx_data; assign req = rd_req | wr_req; assign dout = ({8{rd_req}} & rd_tx_data) | ({8{wr_req}} & wr_tx_data); // 读控制模块 spi_read_ctrl u_spi_read_ctrl( /* input */.clk (clk ), /* input */.rst_n (rst_n ), /* input [2:0] */.key_out (key_out ), /* input [7:0] */.din (din ), /* input */.done (done ), /* output */.req (rd_req ), /* output [7:0] */.dout (rd_tx_data), /* output reg [23:0]*/.seg_data(seg_data) ); // 写控制模块 spi_write_ctrl u_spi_write_ctrl( /* input */.clk (clk ), /* input */.rst_n (rst_n ), /* input [2:0] */.key_out (key_out), /* input [7:0] */.din (din ), /* input */.done (done ), /* output */.req (wr_req ), /* output [7:0] */.dout (wr_tx_data) ); endmodule
module top( input clk, input rst_n, input [2:0] key_in, output [7:0] seg_dig, output [5:0] seg_sel, input miso,// 主机采样从机发送 output mosi,// 主机发送从机 output sclk,// 串行时钟 output cs_n // 片选信号 ); wire [2:0] key_out; wire req; wire done; wire [7:0] rx_data; wire [7:0] tx_data; wire [23:0] seg_data; // 按键消抖模块 key_filter u_key_filter( /* input */.clk (clk ), /* input */.rst_n (rst_n ), /* input [KEY_W-1:0] */.key_in (key_in ), /* output reg [KEY_W-1:0] */.key_out (key_out) ); // 数码管驱动 seg_driver u_seg_driver( /* input */.clk (clk ), /* input */.rst_n (rst_n ), /* input [23:0] */.data (seg_data), /* output reg [7:0] */.seg_dig (seg_dig), /* output reg [5:0] */.seg_sel (seg_sel) ); spi_control u_spi_control( /* input */.clk (clk ), /* input */.rst_n (rst_n ), /* input [2:0] */.key_out (key_out ), /* input [7:0] */.din (rx_data ), /* input */.done (done ), /* output [7:0] */.dout (tx_data ), /* output */.req (req ), /* output [23:0] */.seg_data (seg_data) ); spi_interface u_spi_interface( /* input */.clk (clk ), /* input */.rst_n (rst_n), /* // 接口与主机 */ /* input [7:0] */.din (tx_data), /* input */.req (req ), /* output [7:0] */.dout (rx_data ), /* output */.done (done ), /* // 接口与flash */ /* input */.miso (miso ),// 主机采样从机发送 /* output */.mosi (mosi ),// 主机发送从机 /* output */.sclk (sclk ),// 串行时钟 /* output */.cs_n (cs_n ) // 片选信号 ); endmodule
按键消抖模块
数码管驱动模块
这个还没仿真,仿真验证也是验证接口状态转移是否正确
读id数据
读数据
这个spi我是自己实现的,但是实现的是最基本的三个功能,读id,读数据,写数据,但是每次功能按键还要复位一下,肯定有bug,但是不改了,哈哈。这个我只是给读者提供思路,但是不要直接拿我这个bug代码,日后我会上传正确的好的代码。
记得Verilog代码要看时序,有时候一个时序对不上,真的,调试半天。像我写数据的时候,因为串行时钟的时序没有对上相对应的状态,晚了一个周期,按照spi协议的说明,应该是在边沿发送数据,晚了一个周期,就导致数据不对了,所以无论咋样都没写进数据。
我写博客是我的思路,我的理解,也可能是我与老师的结合,不完全是老师的,所以有问题很正常。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。